Sommaire

BONJOUR
Musique "Invention BWV784" J.S. BACH 1685-1750

Tutoriel et Help

API Windows en C


Cours sur L'API Winodos de openclassrooms

Windows API

Un article de Wikipédia, l'encyclopédie libre.

Windows API
Développeur | Microsoft
Environnement | Windows
Type | interface de programmation
Politique de distribution | incorporé dans Windows
modifier Consultez la documentation du modèle

Win16, Win32 et Win32s sont des ensembles normalisés de fonctions qui permettent aux logiciels applicatifs de se servir des fonctionnalités des différents systèmes d'exploitation de la famille Windows. Win32 est l'ensemble de fonctions le plus souvent utilisé[1], et les programmes dits pour Windows sont généralement des programmes utilisant Win32.

Win32, successeur de Win16, a été introduit en 1993, dans les produits 32 bits de la famille Windows comme Windows NT, Windows 2000, Windows 95 ou leurs successeurs. Cette interface de programmation est mise en œuvre par 3 bibliothèques logicielles : Kernel32.dll, User32.dll et GDI32.dll[2]. Les mêmes fonctions de Win32 sont disponibles dans tous les produits Windows et, suivant le produit, l'utilisation de certaines fonction peut aboutir à une erreur service non disponible[1].

Les fonctions de Win32 permettent notamment de manipuler des processus, de communiquer entre programmes, d'exploiter les réseaux informatiques, de manipuler des fichiers, des imprimantes, des serveurs et des ports de communication[1].

Un kit de développement (SDK) est disponible pour Windows qui fournit la documentation et les outils pour permettre aux développeurs de créer des logiciels en utilisant cette API et les technologies associées.

Sommaire

  • 1 Histoire
  • 2 Technique
  • 3 Composant de Windows API
    • 3.1 Services de base
    • 3.2 Interface graphique
    • 3.3 Interface utilisateur
    • 3.4 Boîtes de dialogue communes
    • 3.5 Bibliothèque de contrôles communs
    • 3.6 Shell Windows
    • 3.7 Services réseau
    • 3.8 Autres API liées
  • 4 Versions
  • 5 Autres implémentations
  • 6 Notes et références
  • 7 Liens externes

Histoire

Les produits Windows parus avant 1993 étaient adaptées aux processeurs 16 bits tels que le Intel 80286 ou ses prédécesseurs[3]. Les éditions de Windows postérieures à cette date sont adaptées aux processeurs 32 bits tels que le Intel 80386 et ses successeurs[3]. Les différences entre ces produits sont notamment la manipulation des pointeurs et l'organisation de l'espace d'adressage[3].

Le nom Win32 a été donné à l'interface de programmation mise à disposition dans Windows NT et ses successeurs, les éditions 32 bits de Windows, pour la distinguer de la précédente interface Win16 inclue dans les éditions 16 bits (Windows 3.1 et ses prédécesseurs)[1]. Win32 offre des fonctions similaires à celles qui existant dans Win16, ainsi qu'un lot de fonctions qui sont couramment mises à disposition par les bibliothèques standard de C [1].

À ses débuts, Windows NT, première édition 32 bits de Windows, offrait l'interface de programmation de OS/2 Presentation Manager. Puis cette interface a été modifiée en vue de s'approcher de celle qui existait dans les versions précédentes ; cette interface a été baptisée Win32, et son prédécesseur Win16[2].

Win32s est une interface de programmation qui offre une partie des fonctions de Win32, et est destinée aux programmes pour les produits 16 bits de la série Windows. L'utilisation de certaines fonctions de Win32s peut aboutir à une erreur non disponible[1].

Lors de la sortie de Windows NT et l'introduction de l'interface Win32, très peu de logiciels applicatifs utilisaient cette technique. Microsoft a équipé Windows NT de plusieurs environment subsystems permettant de l'utiliser pour exécuter des logiciels destinés à MS-DOS, OS/2, Unix (POSIX) ainsi que les précédentes versions de Windows qui implémentent l'interface Win16.

Technique

Les interfaces de programmation Win16 et Win32 font partie des environment subsystem de produits de la famille Windows[4]. Chaque environnement permet l'exécution de programmes prévus pour un certain type de système d'exploitation : Win32 pour les programmes prévus pour les éditions 32 bits de Windows, Win16 pour les autres éditions (antérieures à 1993), POSIX pour de nombreux programmes prévus pour Unix, ou encore l'environnement pour des programmes destinés à OS/2 [4].

Un environment subsystem est essentiellement un ensemble de bibliothèques logicielles qui exposent les fonctions de l'interface de programmation ainsi qu'un programme qui assure la communication entre le logiciel applicatif et le cœur du système d'exploitation. Les fonctions du noyau du système d'exploitation ne sont pas directement exploitées par les logiciels applicatifs.

Win32 est l'environnement natif de Windows NT et ses successeurs. Les autres environnements s'appuient dessus pour émuler les conditions d'exécution des applications : MS-DOS est pris en charge par le programme VDM pour Virtual DOS machine, Windows 3.1 est pris en charge par le programme WOW pour Windows on Windows, Unix est pris en charge par émulation de l'interface POSIX.

Windows 95, Windows 98 et Windows NT offrent les deux interfaces Win16 et Win32[3]. Lorsqu'une fonction de Win32 est utilisée dans Windows 95, cela entraîne l'utilisation de la fonction Win16 équivalente. Dans Windows NT c'est le contraire : l'utilisation d'une fonction Win16 entraîne l'utilisation de la fonction Win32 équivalente[3].

Composant de Windows API

Les fonctionnalités fournies par Windows API peuvent être rangées dans sept catégories :

Services de base

Donne accès aux ressources fondamentales du système disponibles sous Windows. Exemples : Système de fichiers, périphériques, processus, processus léger, accès au registre système et au Système de gestion d'exceptions. Ces fonctions sont stockées dans les fichiers kernel.exe, krnl286.exe ou krnl386.exe pour les systèmes 16-bits et kernel32.dll et advapi32.dll sur les systèmes 32 bits.

Interface graphique

Permet d'accéder aux ressources pour l'affichage sur les moniteurs, imprimantes, etc. Elle est stockée dans le fichier gdi.exe sur les systèmes 16-bits et gdi32.dll sur les systèmes 32-bits.

Interface utilisateur

Permet d'afficher et de gérer les contrôles de base comme les boutons et barres de défilement, de recevoir les informations du clavier et de la souris et des fonctionnalités associées comme l'Environnement graphique. Ces fonctionnalités sont stockées dans le fichier user.exe sur les systèmes 16-bits, et user32.dll comctl32.dll sur les systèmes 32-bits. Depuis Windows XP, les contrôles ont été regroupés dans comctl32.dll, dédiant user32.dll pleinement aux infrastructures.

Boîtes de dialogue communes

Permet d'afficher les Boîtes de dialogue pour ouvrir et enregistrer des fichiers, choisir la couleur et la police, etc. Elle réside dans le fichier comdlg.dll sur les systèmes 16-bits et comdlg32.dll sur les systèmes 32-bits. Elle a été groupée avec la partie Interface utilisateur.

Bibliothèque de contrôles communs

Donne accès à des fonctions avancées du système d'exploitation comme des barres de statut (situées au bas des fenêtres), barres de progression, barres d'outils et onglets. Ces commandes résident dans la bibliothèque logicielle commctrl.dll sur les systèmes 16-bits et comctl32.dll. Elle est groupée avec l'interface utilisateur.

Shell Windows

Le composant de Windows API qui permet à des applications d'accéder aux fonctionnalités fournies par le shell du système d'exploitation. Elle réside dans le fichier shell.dll sur les systèmes 16-bits et shell32.dll sur les systèmes 32-bits, et depuis Windows 95 dans le fichier shlwapi.dll. Elle a été groupée avec la partie Interface utilisateur.

Services réseau

Donne accès aux diverses possibilités de gestion de réseau du système d'exploitation. Ses sous-composants incluent NetBIOS, Winsock, RPC, etc. Sur les anciennes versions de Windows, cela incluait NetDDE.

Autres API liées

Internet Explorer offre quelques API qui sont souvent utilisées par les programmes et peut donc être considéré comme une partie de Windows API. Internet Explorer est fourni d'office depuis Windows 98. Il est prévu d'arrêter cette prise en charge avec Vista.

Depuis Windows 95, il y aussi des API pour les affichages graphiques qui permettent d'avoir accès aux ressources de DirectX.

Il y a aussi d'autres API pour les relations entre les applications comme DDE (Dynamic Data Exchange, Echange dynamique de données), OLE (Object Linking and Embedding) (voir (en) Object Linking and Embedding) et COM (Component Object Model).

Versions

Chaque version de Windows a apporté son lot de modifications de l'API. Malgré cela, le nom de l'API reste le même sauf pour les modifications majeures. Microsoft a quand même changé le nom Win32 en Windows API pour les contenir dans une seule famille même pour une prochaine modification de celle-ci.

  • Win16 était la première API pour les versions 16-bits du système. Elle était au départ simplement nommé Windows API mais a été renommé en Win16 pour permettre le passage au 32-bits de Windows API. Les fonctionnalités étaient principalement dans le noyau du système : kernel.exe (ou krnl286.exe ou krnl386.exe), user.exe et gdi.exe. Malgré l'extension exe, ces fichiers sont des bibliothèques de liens dynamiques.
  • Win32 est la version 32-bits de l'API pour les systèmes plus récents. L'api se compose d'implémentations, comme avec Win16, dans le système bibliothèques de liens dynamiques. Le noyau de Win32 réside dans les fichiers kernel32.dll, user32.dll, et gdi32.dll. Win32 a été présenté avec Windows NT. La version embarquée avec Windows 95 était appelée Win32c, avec « c » pour compatibilité (compatibility en anglais) mais ce nom a été abandonné pour Win32. Dans la version Windows NT 4.0 et suivantes, les appels de Win32 sont exécutés par deux modules, csrss.exe (Client/Server Runtime Server Subsystem) en mode utilisateur et win32k.sys en mode noyau.
  • Win32s est une extension de Win32 pour les systèmes Windows 3.x qui a été introduite comme sous-ensemble de Win32. Le « s » est pour sous-ensemble (subset en anglais).
  • Win32 pour les éditions 64bits précédemment appelée Win64 est la version pour les ordinateurs 64-bits, avec les versions Windows XP Professional x64 Edition pour les processeurs x86-64 ainsi que Windows XP 64-bit Edition et Windows Server 2003 pour les processeurs Itanium. Cette version apporte juste le support pour ces deux nouvelles plateformes.

Autres implémentations

Bien que Windows API soit soumise aux droits d'auteurs et malgré les précédents légaux aux États-Unis, il est permis d'émuler cette API sur d'autres plateformes.

C'est le cas par exemple de Wine qui émule une API compatible avec Win32 pour les systèmes d'exploitation à base UNIX. Un autre exemple est le système ReactOS.

Notes et références

  1. ↑ a, b, c, d, e et f (en)Marshall Brain et Ron Reeves, Win32 System Services: The Heart of Windows 98 and Windows 2000,Prentice Hall Professional - 2001,(ISBN 9780130225573)
  2. ↑ a et b (en) Ken Henderson,The Guru's Guide to SQL Server Architecture and Internals,Addison-Wesley Professional - 2004,(ISBN 9780201700473)
  3. ↑ a, b, c, d et e (en)Charles Petzold, Programming Windows®, O'Reilly Media Inc. - 2010,(ISBN 9780735646032)
  4. ↑ a et b (en)Jeffrey R. Shapiro et Jim Boyce, Windows Server 2003 Bible, John Wiley & Sons - 2006,(ISBN 9780471998044)

Liens externes

  • Windows API Reference sur MSDN : la référence à la source.
  • news://fr.comp.os.ms-windows.programmation/ Newsgroup français dédié à la programmation des API Win32
  • http://www.charlespetzold.com/pw5/ Livre de référence indispensable pour acquérir les bases

· Portail de la programmation informatique Portail de la programmation informatique

· Portail de Microsoft Portail de Microsoft

Ce document provient de « http://fr.wikipedia.org/w/index.php?title=Windows_API&oldid=103320569 ».

Catégorie :

  • API Microsoft

Catégories cachées :

  • Portail:Programmation informatique/Articles liés
  • Portail:Informatique/Articles liés
  • Portail:Microsoft/Articles liés


Api Windows : Raccourcis clavier.

API Windows en C

Vu dans ... http://chgi.developpez.com/windows/ voir ci-dessous

I. Sommaire

Les bases :

- La fenêtre principale.
- Contrôles et messages.
- Les commandes et notifications.
- Les boîtes de dialogue communes.
- Les contextes de périphériques.
- Dessiner.
- Les ressources.
- Les boîtes de dialogue personnalisées.
- Communiquer avec les boîtes de dialogue.
- Les accélérateurs clavier.
- 11 - Une boîte de dialogue comme fenêtre principale.
- Les contrôles supplémentaires de Windows.
- Donner le style XP à vos applications.
- Sous-classement de fenêtres.
- Afficher une image.

Les boîtes de dialogue :

- la boîte de dialogue d'ouverture de fichier. (Chap. 4) .
- la boîte de dialogue de sauvegarde.
- la boîte de dialogue de choix de polices de caractères.
- la boîte de dialogue de choix de couleurs.

Les contrôles :

- Le contrôle d'édition. (Chap. 9) .
- Le contrôle CheckBox ou case à cocher.
- Le contrôle Radiobutton ou bouton radio.
- Le contrôle ListBox ou liste de choix.
- Le contrôle ComboBox ou liste déroulante.
- Le contrôle Scroll bar ou ascenseur.
- Le contrôle Static.

- Le contrôle Progress Bar ou Barre de progression. (Chap. 12) .
- Le contrôle Status Bar ou Barre d'état.
- Le contrôle TrackBar ou Slider.
- Le contrôle List View.
- Le contrôle Tree View.
- Le contrôle Tab ou boîte à onglets.
- Le contrôle Toolbar.
- Le contrôle ToolTips.

- Le contrôle PropertySheet.

Divers :

- Les Timers.
- Les menus contextuels.
- Les boîtes de dialogues non modales.
- Path and clip.
- Le Tray Icon.
- numération de ressources.
- Afficher une image sous Windows NT.
- Les services NT.
- Une seule instance pour l'application.

Exemples complet en pure API Win32:

- Un démineur.
- Un mastermind.
- Black To White. (Jeu tout simple, mais pas facile).

Bonne lecture, CGi.

C/C++
Les pointeurs du C/C++. Les listes chaînées. Liste simple. Liste triée. Liste double. Les arbres. Les tas. Le C orienté objets ?
API Windows
1 - La fenêtre principale. 2 - Contrôles et messages. 3 - Les commandes. 4 - Dialogue std. 5 - Contexte de périph. 6 - Dessiner. 7 - Les ressources. 8 - Dialogue perso. 9 - Dialogue comm. 10 - Les accélérateurs.
... plus.
C++ BUILDER
Trucs et astuces. Composant. TRichEdit. TDrawGrid. Application MDI. TThread. wxWidgets. Style Win XP.
wxWidgets
Première application. Construire un menu. Dessiner. Sisers, Timers... Dialogues standards. Dialogues perso.
DotNet


Api Windows : Raccourcis clavier.

1 - La fenêtre principale.

I. Introduction :

Windows est un système d'exploitation proposant une interface graphique. Dans ce premier article, nous allons créer une application composée seulement d'une fenêtre principale, dont le but et de montrer l'architecture d'un programme Windows.

Image non disponible

Prenez garde au terme de fenêtre, dans Windows. Il signifie objet visuel, donc un bouton est une fenêtre ! Celle apparaissant au démarrage de l'application est souvent appelée fenêtre principale. Nous emploierons tout de même le mot fenêtre pour désigner une fenêtre du type de la fenêtre principale et le mot contrôle pour désigner les objets visuels qu'elles peuvent contenir...

II. La fonction WinMain :

Le point d'entrée d'une application Windows est la fonction WinMain. C'est l'équivalent de la fonction main des applications classiques. Elle est appelée par le système d'exploitation au lancement du programme. Il lui fournit 4 paramètres.

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                 LPSTR lpCmdLine, int nCmdShow);
  • Le premier paramètre est le handle d'instance de l'application. C'est un numéro unique attribué par le système d'exploitation qui lui permet de l'identifier.
  • Le second paramètre est toujours NULL pour les applications Win32.
  • Le troisième paramètre est un pointeur sur la ligne de commande.
  • Et enfin le quatrième et dernier paramètre est un identificateur qui indique comment Windows désire afficher la fenêtre (normal, caché, minimisé ...).

C'est donc dans cette fonction (WinMain) que nous allons créer la fenêtre principale.

III. Création de la fenêtre :

La fonction qui permet de créer une fenêtre se nomme CreateWindow :

HWND CreateWindow(
    LPCTSTR lpClassName,  // Pointeur sur une classe de fenêtre.
    LPCTSTR lpWindowName,  // Pointeur sur le texte de la fenêtre.
    DWORD dwStyle,  // Style de la fenêtre.
    int x,  // Position horizontale de la fenêtre.
    int y,  // Position verticale de la fenêtre.
    int nWidth,  // Largeur de la fenêtre.
    int nHeight,  // Hauteur de la fenêtre.
    HWND hWndParent,  // Handle de la fenêtre parent.
    HMENU hMenu,  // Handle de menu ou ID de contrôle.
    HANDLE hInstance,  // Handle d'instance de l'application.
    LPVOID lpParam   // Pointeur sur des données passées à WM_CREATE.
   );

En voici un exemple d'utilisation :

    HWND hwnd;

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
                                                   NULL, NULL, hinstance, NULL);

Le premier paramètre qu'elle reçoit est un pointeur sur une chaîne de caractères identifiant la classe de fenêtre. Les classes de fenêtre sont des modéles pour construire les fenêtres (le terme de classe n'a rien à voir avec les classes du C++). Si pour les contrôles standard nous avons des classes de fenêtres prédéfinies et globale, nous devrons en créer une pour la fenêtre principale. Nous devons pour cela remplir une structure de type WNDCLASS (définie dans winuser.h)

    WNDCLASS wc;

    wc.style = 0;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "MaWinClass";

Les champs qu'il nous importe d'affecter pour l'instant à cette structure sont :

  • Le champ lpszClassName avec un pointeur sur la chaîne de caractères identifiant la classe de fenêtre. Celle que l'on a passé comme paramètre à la fonction CreateWindow.
  • Le champ hInstance sera initialisé avec le handle d'instance hinstance que nous a fourni la fonction WinMain.
  • Le champ hIcon avec le handle d'une icône standard de Windows qui nous est retournée par la fonction LoadIcon.
  • Le champ hCursor avec le handle d'un curseur prédéfini de Windows (en l'occurrence la flèche) qui nous est retourné par la fonction LoadCursor.
  • Le champ hbrBackground avec le handle d'un pinceau qui servira à peindre la surface client de la fenêtre. Dans cet exemple nous lui affecterons une couleur prédéfinie du système (1 + (l'identificateur de la couleur)).
  • Le champ lpfnWndProc de type WNDPROC reçoit un pointeur de fonction sur la procédure de fenêtre que nous allons voir plus loin dans ce document.

Nous passons sous silence pour l'instant les autres champs de cette structure.

Remplir cette structure ne suffit pas, il faut aussi l'enregistrer au niveau du sytème (souvenez-vous qu'elles sont identifiées par une chaîne de caractères). Cet enregistrement sera effectué par la fonction RegisterClass qui reçoit comme paramètre l'adresse de la variable wc de type WNDCLASS créée précédemment :

     RegisterClass(&wc);

Revenons-en maintenant à la fonction CreateWindow.

  • Son deuxième paramètre reçoit un pointeur sur le texte de la fenêtre. Dans le cas de la fenêtre principale, ce sera le texte du titre (si cela avait été un bouton, le texte se serait affiché sur le bouton).
  • Le troisième paramètre est le style de la fenêtre. Ce sont des identificateurs qui sont défini dans le fichier winuser.h. Nous ne les passerons pas en revue car ils sont trop nombreux. Dans l'exemple, nous lui donnerons la valeur WS_OVERLAPPEDWINDOW. Ce paramètre permet d'avoir une fenêtre avec une barre de titre, un menu system, les boutons minimiser et maximiser et que l'on peut redimensionner.
  • Les deux paramètres suivants sont la position à l'écran de la fenêtre par rapport au coin haut gauche du bureau. Dans l'exemple, ils ont la valeur par défaut fournie par Windows : CW_USEDEFAULT.
  • Les deux paramètres suivants sont la taille de la fenêtre : largeur et hauteur. Ils peuvent aussi avoir la valeur par défaut CW_USEDEFAULT.
  • Le paramètre suivant désigne le parent de la fenêtre. Il est utile pour les contrôles enfants. En ce qui concerne la fenêtre principale nous l'initialiserons à NULL.
  • Le paramètre suivant désigne un menu pour la fenêtre, il peut être à NULL s'il n'y a pas de menu ou si le menu a été référencé dans le champ lpszMenuName de la classe de fenêtre.
  • L'avant-dernier paramètre reçoit le handle d'instance de l'application qui crée la fenêtre, celui qui est fourni par WinMain.
  • Le dernier paramètre sert à passer des données à la procédure de fenêtre quand elle reçoit un message WM_CREATE. Dans cet exemple, il sera initialisé à NULL.

Après s'être exécutée, la fonction CreateWindow nous retourne le handle de la fenêtre qu'elle vient de créer. Les handles de fenêtre sont des identificateurs uniques attribués par Windows qui désignent chaque fenêtres. Ils sont de type HWND.

Notre fenêtre étant maintenant créée, il faut la rendre visible par l'appel de la fonction ShowWindow :

     ShowWindow(hwnd, nCmdShow);

Cette fonction reçoit comme paramètre le handle de la fenêtre et un identificateur qui indique comment on désire l'afficher (normal, caché, minimisé ...). En général, on lui donne celui fourni par WinMain (voir plus haut dans ce document).

Ensuite, on appelle UpdateWindow avec comme paramètre le handle de la fenêtre. Cette fonction sert à rafraîchir l'affichage de la zone client. Dans cet exemple, elle n'est pas vraiment utile puisque la zone client est vide.

     UpdateWindow(hwnd);

IV. La boucle de messages :

Revenons au fonctionnement de Windows. Pour communiquer avec une application ou ses diverses fenêtres, Windows leurs envoie des messages. Par exemple, si vous cliquez sur le bouton fermeture (en haut à droite de la fenêtre), Windows va créer un message approprié qu'il va envoyer dans la file d'attente de l'application. La file d'attente est un tampon où sont stockés les messages en attente de traitement.

C'est à nous de coder l'extraction des messages de la file d'attente :

    MSG msg;

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

C'est le rôle de la fonction GetMessage, dont le premier paramètre est un pointeur sur une variable de type MSG où elle doit stocker les données du message qu'elle vient de récupérer.

Nous devons donc définir cette variable de type MSG, qui est une structure composé d'un champ hwnd qui contient le handle de la fenêtre à qui il est destiné, un champ message identifiant le message (par exemple WM_CLOSE pour demander à la fenêtre de ce fermer) et de deux champs wParam et lParam contenant des données ayant rapport au message et de significations différentes selon le message.

Revenons à GetMessage,

  • Son deuxième paramètre est le handle de fenêtre dont on veut récupérer les messages. Il sera mis à NULL car on veut récupérer tous les messages de l'application (destinés à toutes ses fenêtres).
  • Les deux derniers paramètres seront mis à zéro, ils servent au filtrage des messages.

GetMessage renvoie toujours la valeur TRUE sauf si elle vient de récupérer le message WM_QUIT, ce qui met fin à la boucle de message donc à l'application.

Ensuite on passe le message à la fonction TranslateMessage qui traduit les messages WM_KEYDOWN (appui des touches clavier) en message WM_CHAR avec le code de caractère correspondant dans le champ wParam du message.

Ensuite, c'est à la fonction DispatchMessage de traiter le message. Son rôle est de l'envoyer à la procédure de fenêtre. Celle à qui il est destiné.

V. La procédure de fenêtre :

Voici la procédure de fenêtre de notre fenêtre principale dont nous avions passé un pointeur à la classe de fenêtre au début de ce document (on passe ce pointeur à la classe de fenêtre car c'est nous qui la créons, mais le système d'exploitation qui l'appelle, il doit donc la localiser).

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_CREATE:

            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

Les paramètres qu'elle reçoit sont les données du message en cours de traitement. Celui qui a été envoyé par DispatchMessage. C'est à nous, développeurs, d'implémenter des actions en fonction du message que la procédure de fenêtre reçoit. Dans cet exemple, un seul message est traité : WM_DESTROY. Il nous indique que la fenêtre est fermée et a été détruite. Comme il s'agit de la fenêtre principale de l'application, il est d'usage de fermer l'application. Ce que nous ferons en appelant la fonction PostQuitMessage qui a pour fonction de poster un message WM_QUIT dans la file d'attente. Qui comme nous l'avons vu plus haut dans ce document, met fin à la boucle de messages et donc à l'application. Les messages non traités doivent l'être par la fonction DefWindowProc. Fonction qui implémente le comportement par défaut d'une fenêtre.

VI. Code complet :

#include <windows.h>

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG msg;
    WNDCLASS wc;

    wc.style = 0;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
                                                   NULL, NULL, hinstance, NULL);
    if (!hwnd) return FALSE;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_CREATE:

            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

Ce code a été testé sous VC++, CodeBlocks(Mingw), C++ Builder.

A vos PC.

CGi

C/C++
Les pointeurs du C/C++. Les listes chaînées. Liste simple. Liste triée. Liste double. Les arbres. Les tas. Le C orienté objets ?
API Windows
1 - La fenêtre principale. 2 - Contrôles et messages. 3 - Les commandes. 4 - Dialogue std. 5 - Contexte de périph. 6 - Dessiner. 7 - Les ressources. 8 - Dialogue perso. 9 - Dialogue comm. 10 - Les accélérateurs.
... plus.
C++ BUILDER
Trucs et astuces. Composant. TRichEdit. TDrawGrid. Application MDI. TThread. wxWidgets. Style Win XP.
wxWidgets
Première application. Construire un menu. Dessiner. Sisers, Timers... Dialogues standards. Dialogues perso.
DotNet


Api Windows : Raccourcis clavier.

API Windows en C
2 - Contrôles et Messages.

Introduction :

Dans ce chapitre, nous allons revenir sur le traitement des messages.
Nous allons aussi créer un contrôle enfant que nous allons mettre sur la fenêtre principale.
Ce contrôle sera un contrôle d'édition multi lignes. Il devra se positionner sur toute la surface client de la fenêtre principale (on appelle surface client d'une fenêtre toute sa partie intérieure, c'est-à-dire la fenêtre sans sa bordure et sa barre de titre).

Création d'un contrôle :

Comme nous l'avons dit au chapitre précédent un contrôle est une fenêtre. Nous allons donc utiliser comme pour la fenêtre principale la fonction CreateWindow pour le créer.

     HWND hEdit;
     hEdit =CreateWindow("edit", "Texte",
              WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
                                           0, 0, 0, 0, hwnd, NULL, hinst, NULL);

Pour les contrôles, les classes de fenêtres sont globales et définies par Windows. Celle du contrôle d'édition s'appelle : "EDIT". Nous la mettrons donc comme premier paramètre de CreateWindow.
Son deuxième paramètre attend un pointeur sur une chaîne de caractères au cas ou vous désirez qu'il contienne du texte dès sa création.
Le troisième attend les identificateurs de style. Ils sont ici complètement différents de ce qu'on avait mis pour la fenêtre principale :
WS_VISIBLE rend la fenêtre visible dés sa création.
WS_CHILD en fait un contrôle enfant. C'est obligatoire si l'on veut qu'il soit à l'intérieur de notre fenêtre.
ES_MULTILINE multi lignes si on veut avoir la possibilité de saisir plusieurs lignes.
ES_WANTRETURN autorise le retour à la ligne automatique.
WS_VSCROLL pour lui mettre un ascenseur vertical.
(Les identificateurs de style étant très nombreux, je vous propose de consulter l'aide Win32 API CreateWindow, ou CreateWindowEx qui permet des styles supplémentaires).
Les quatre paramètres suivants sont comme pour la fenêtre principale, sa position et ses dimensions. Dans le code complet de l'exemple, elles sont toutes mises à zéro car le contrôle sera redimensionné automatiquement à chaque changement de dimensions de la fenêtre principale (voir plus loin dans ce document).
Le paramètre suivant est le handle de la fenêtre principale car elle en est le parent.
Et comme pour la fenêtre principale le handle d'instance de l'application pour l'avant dernier paramètre.
Dans le code de l'exemple, pour des raisons de simplicité, j'ai créé une variable globale pour le handle d'instance que j'ai initialisé dans la fonction WinMain.

Les messages :

Nous avons dit que notre contrôle d'édition devait se redimensionner pour occuper la surface client de la fenêtre principale. Mais comment savoir que les dimensions de la fenêtre principale ont changé ? Par l'intermédiaire d'un message ! Si un utilisateur a changé les dimensions de la fenêtre, Windows va envoyer un message à cette fenêtre pour l'en informer. Dans ce cas précis le message est WM_SIZE. C'est à nous de traiter ce message dans la procédure de fenêtre :

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HWND hEdit;

    switch (uMsg)
    {
        case WM_CREATE:
            hEdit =CreateWindow("edit", "Texte",
              WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
                                           0, 0, 0, 0, hwnd, NULL, hinst, NULL);
            return 0;

        case WM_SIZE:
            MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

Comme vous pouvez le voir dans l'exemple nous créons le contrôle d'édition à l'arrivée du message WM_CREATE, message envoyé par Windows quand la création de la fenêtre principale est terminée, mais pas encore visible. A la réception du message WM_SIZE, nous récupérons les dimensions de la surface client de la fenêtre dans le paramètre lParam joint au message. La largeur étant dans son mot de poids faible et la hauteur dans son mot de poids fort. Nous les extrayons à l'aide des macros LOWORD et HIWORD. Et nous affecterons ces dimensions au contrôle d'édition à l'aide de la fonction MoveWindow (son dernier paramètre à TRUE informe Windows qu'il doit redessiner le contrôle après l'appel de la fonction).
Son premier paramètre étant le handle du contrôle d'édition, car c'est lui dont on change les dimensions.
Il est évident que l'on ne va pas passer tous les messages en revue, vous pouvez pour cela consulter l'aide sur L'API Win32. Cet exemple vous donne déjà une idée de la manière dont on intercepte les événements dans Windows. Nous aurons l'occasion de voir et revoir les messages dans les chapitres suivants. Il sont omniprésent dans Windows. Le moindre événement qui survient et c'est un message qui est envoyé. Il est donc important d'être bien documenté, notament sur les divers messages existants. Et ils sont nombreux.
Comme dit au chapitre précédent, les messages non traités doivent l'être par la fonction DefWindowProc. C'est une procédure de fenêtre interne qui traite les messages afin de faire le minimum vital pour la fenêtre. Ne serait ce que simplement l'afficher.

Structure MSG :

typedef struct tagMSG {
    HWND   hwnd;	// Handle de la fenêtre de destination.
    UINT   message;     // Le message proprement dit.
    WPARAM wParam;      // Paramètre dépendant du message.
    LPARAM lParam;      // Paramètre dépendant du message.
    DWORD  time;        // Heure ou il a été posté.
    POINT  pt;          // Coordonnées du curseur au moment ou il a été posté.
} MSG;

Code complet :

#include <windows.h>

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                  LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG msg;
    WNDCLASS wc;

    hinst = hinstance;

    wc.style = 0 ;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
                                                   NULL, NULL, hinstance, NULL);
    if (!hwnd)  return FALSE;

    ShowWindow(hwnd, nCmdShow);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HWND hEdit;

    switch (uMsg)
    {
        case WM_CREATE:
            hEdit =CreateWindow("edit", "Texte",
              WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
                                           0, 0, 0, 0, hwnd, NULL, hinst, NULL);
            return 0;

        case WM_SIZE:
            MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

Dans le code de cet exemple j'ai mis le pinceau à NULL (wc.hbrBackground = NULL) dans la classe de fenêtre de la fenêtre principale, sinon un il y a scintillement désagréable sur l'ascenseur du contrôle d'édition lors des redimensionnements.

Ce code a été testé sous VC++, CodeBlocks(Mingw), C++ Builder, DevC++.

A vos PC. CGi

Avec la contribution de Bestiol pour la relecture.


Api Windows : Raccourcis clavier.

API Windows en C
3 - Les commandes et notifications.

Introduction :

Dans ce chapitre nous allons aborder un message particulier : WM_COMMAND. Ce message sert à envoyer des commandes. Une commande est envoyé suite à l'action d'un contrôle par l'utilisateur. C'est-à-dire par exemple le click d'un bouton ou d'une option d'un menu... Nous devons donc l'intercepter pour implémenter l'action que nous voulons faire. Dans l'exemple nous créerons un menu qui va nous permettre d'envoyer des commandes utilisateur aux fenêtres de l'application.
Nous verrons aussi qu'un contrôle peut envoyer des informations à l'application avec ce message quand des événements propre à lui même se produisent. On les appelle des notifications.
Nous aborderons aussi l'envoi de messages par programme.

Création du menu :

Pour préparer notre exemple nous allons d'abord créer le menu. Il existe deux solutions pour créer des menus, soit par programme, soit à l'aide de ressources. Les ressources feront l'objet d'un autre chapitre. Nous allons donc utiliser la première solution (à savoir qu'il est plus courant d'utiliser les ressources pour construire un menu).

     #define IDM_QUIT 1

     HMENU hMenu, hSousMenu;

     hSousMenu = CreateMenu();
     AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");
     hMenu  = CreateMenu();
     AppendMenu(hMenu,MF_POPUP,(UINT)hSousMenu,"Fichier");

Les menus sont créés avec la fonction CreateMenu qui renvoie un handle de menu (HMENU). Dans l'exemple nous créons un menu principal et un sous-menu. Nous créons d'abord le sous-menu car il devra être ajouté au menu principal. Nous lui ajoutons une option de menu à l'aide de la fonction AppendMenu. Nous pouvons utiliser cette fonction autant de fois que l'on a d'options à rajouter. Elle reçoit comme paramètres :
Le handle de menu auquel on veut l'ajouter.
Une constante indiquant son aspect (MF_STRING car c'est une chaîne de caractère).
L'identificateur de l'option qui la distinguera parmi les autres. C'est une constante de type UINT (unsigned int) que l'on peut nommer pour plus de clarté dans le code (#define IDM_QUIT 1).
Le dernier paramètre est un pointeur sur la chaîne de caractères représentant l'option visuellement dans le menu ("Quitter" dans l'exemple).
Nous créons ensuite le menu principal de la même façon. Nous lui ajoutons le sous-menu créé précédemment toujours avec la fonction AppendMenu en lui passant le handle du sous-menu comme paramètre(troisième paramètre de AppendMenu). On lui donnera un aspect de type popup (deuxième paramètre de AppendMenu à MF_POPUP).
Maintenant, il faut l'ajouter à la fenêtre principale de l'application. Dans l'exemple nous le ferons lors de la création de la fenêtre en passant son handle comme paramètre à la fonction CreateWindow :

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
                                                   NULL, hMenu, hinstance, NULL);

Les commandes :

Comment savoir que l'utilisateur a actionné l'option du menu ?
Et bien en traitant le message WM_COMMAND. Comme nous l'avons dit plus haut, ce message est envoyé par Windows quand une commande utilisateur a été actionnée.
Mais comment savoir que cette commande vient d'une option particulière d'un menu ?
Dans le mot de poids faible du paramètre wParam qui est envoyé avec le message WM_COMMAND : LOWORD(wParam). Il contient l'identificateur que l'on avait attribué à l'option de menu lors de son ajout au menu (IDM_QUIT dans l'exemple) :

    switch (uMsg)
    {
        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
            return 0;

Il suffit alors de faire un test sur ce paramètre si l'on est en train de traiter un message WM_COMMAND et d'agir en consequence.

Envoyer des messages :

Dans cet exemple si l'on a affaire à la commande IDM_QUIT nous demanderons à la fenêtre de se fermer en lui envoyant un message WM_CLOSE avec la fonction PostMessage. Et oui, nous aussi nous pouvons envoyer des messages !
PostMessage permet de déposer des messages dans la file d'attente.
Son premier paramètre est le handle de la fenêtre de destination.
Son second paramètre est l'identificateur du message (WM_CLOSE dans l'exemple).
Les deux paramètres suivants sont les valeurs des champs wParam et lParam du message (wParam et lParam sont inutilisés pour le message WM_CLOSE).
Il existe une autre fonction pour envoyer des messages : SendMessage. Elle reçoit les mêmes paramètres que PostMessage et dans le même ordre. La différence est qu'elle envoie le message directement à la procédure de fenêtre sans passer par la file d'attente. Ensuite, elle attend que le message ai été traité, afin de retourner un résultat. Dans l'exemple nous l'utilisons pour ordonner au contrôle d'édition de changer sa police de caractère :

             HFONT hFont;

             hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
             SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);

Nous avons ici récupéré le handle d'une police de caractère prédéfinie de Windows avec la fonction GetStockObject (Cette fonction permet de récupérer des objets initialisés au démarrage de Windows, tel des pinceaux, crayons...).
Nous allons lui envoyer un second message pour mettre ses marges interieures droite et gauche à 5 pixels :

SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
                                                                MAKELONG(5, 5));

La valeur des marges est envoyée dans un seul paramètre. Il est reconstitué en un DWORD avec la macro MAKELONG. Vous avez du remarquer que le nom de ce message est préfixé des lettre EM au lieu de WM. C'est tout simplement qu'il s'agit d'un message destiné spécifiquement aux contrôles d'édition. EM comme Edit Message et bien sûr WM comme Window Message qui eux s'adresse à tous les types de fenêtres.

Les notifications :

Pendant le déroulement du programme les contrôles envoient des messages WM_COMMAND à leur fenêtre parent. Ceci quand ils subissent certains événements. Prenons comme exemple le contrôle d'édition, à chaque fois que vous modifiez son texte, il envoie un message WM_COMMAND avec le paramètre EN_CHANGE, pour nous le notifier. On les appelle les notifications. Elles sont envoyées dans le mot de poids fort du champ wParam : HIWORD(wParam) du message WM_COMMAND. Je vous recommande de consulter l'aide API Win32 pour chaque contrôle, afin vous documenter sur leurs différentes notifications.

    switch (uMsg)
    {
        case WM_COMMAND:
            if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
            return 0;

Dans l'exemple, à la réception de cette notification nous mémoriserons dans une variable ce changement. Variable que nous testerons à la fermeture de la fenêtre principale afin de proposer une boîte de message pour laisser le choix à l'utilisateur de fermer ou non la fenêtre s'il y a eu modification du texte du contrôle d'édition :

    switch (uMsg)
    {
        case WM_CLOSE:
            if(EditNotChg ||
               MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
                            ,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
                                                            DestroyWindow(hwnd);
            return 0;

Nous interceptons pour cela le message WM_CLOSE. Nous testons la variable EditNotChg si elle est à TRUE, DestroyWindow est appelé, ce qui a pour conséquence d'envoyer un message WM_DESTROY et donc de fermer l'application. Si EditNotChg est à FALSE la fonction MessageBox est appelée si elle retourne IDYES, nous appelons de même DestroyWindow, sinon le programme continue.
MessageBox, vous l'avez compris sert à appeler des boîtes de messages. Son premier paramètre est le handle de fenêtre de son parent. Le deuxième paramètre est le texte du message ou de la question. Le troisième paramètre est son titre. Le quatrième est une constante indiquant son type. (MB_YESNO | MB_ICONQUESTION) dans l'exemple car elle possède un bouton "Oui", un bouton "Non" et l'icône de question (voir l'aide API Win32 pour les autre possibilités).

Code complet :

#include <windows.h>

#define IDM_QUIT 1

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG msg;
    WNDCLASS wc;
    HMENU hMenu, hSousMenu;

    hinst = hinstance;

    wc.style = 0 ;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

    hSousMenu = CreateMenu();
    AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");
    hMenu  = CreateMenu();
    AppendMenu(hMenu,MF_POPUP,(UINT)hSousMenu,"Fichier");

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
                                                   NULL, hMenu, hinstance, NULL);
    if (!hwnd)  return FALSE;

    ShowWindow(hwnd, nCmdShow);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HWND hEdit;
    static BOOL EditNotChg = TRUE;

    switch (uMsg)
    {
        case WM_CREATE:
            {
             HFONT hFont;
             hEdit = CreateWindow("edit", "Texte",
               WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
                                            0, 0, 0, 0, hwnd, NULL, hinst, NULL);
             hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
             SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
             SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
                                                                MAKELONG(5, 5));
             return 0;
            }

        case WM_CLOSE:
            if(EditNotChg ||
               MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
                            ,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
                                                            DestroyWindow(hwnd);
            return 0;

        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
            if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
            return 0;

        case WM_SIZE:
            MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
             return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

Comme vous pouver le constater les message sont omniprésent dans Windows.
Ce code a été testé sous VC++, CodeBlocks(Mingw), C++ Builder, DevC++.

A vos PC.

CGi

Avec la contribution de nico-pyright(c) pour la relecture.


Api Windows : Raccourcis clavier.

API Windows en C
4 - Les boîtes de dialogue communes.

(Ouverture de fichiers.)

Introduction :

Les boîtes de dialogues communes sont celles qui font partie de Windows, comme les boîtes de dialogue d'ouverture ou de sauvegarde de fichier, de choix de polices de caractères, de choix de couleurs... Dans ce premier exemple nous metterons en oeuvre la boîte de dialogue d'ouverture de fichiers. Le principe est semblable pour les autres boîtes de dialogue.

Préparation de l'exemple :

Nous continuons sur la base de l'exemple du chapitre précédent. Nous ajoutons une option avec le texte "Ouvrir..." plus un séparateur au sous-menu. L'option "Ouvrir..." sera identifiée par la constante IDM_OPEN :

#define IDM_QUIT 1
#define IDM_OPEN 2

      hSousMenu = CreateMenu();
      AppendMenu(hSousMenu, MF_STRING, IDM_OPEN, "Ouvrir...");
      AppendMenu(hSousMenu, MF_SEPARATOR, 0, NULL);
      AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");

Comme vu au chapitre précédent, son action sera interceptée dans la procédure de fenêtre avec un message WM_COMMAND :

    switch (uMsg)
    {
        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_OPEN)
              {
               /* Appel de la boîte de dialogue ici. */

La boîte de dialogue commune :

Avant d'appeler une boîte de dialogue commune, nous devont remplir une structure de données la concernant. Elle sera passée comme paramètre à la fonction d'appel. Dans le cas de la boîte de dialogue destinée à l'ouverture de fichiers la structure se nomme OPENFILENAME :

                OPENFILENAME ofn;
                CHAR szFile[MAX_PATH]={0};

                ZeroMemory(&ofn, sizeof(OPENFILENAME));
                ofn.lStructSize = sizeof(OPENFILENAME);
                ofn.hwndOwner = hwnd;
                ofn.lpstrFile = szFile;
                ofn.nMaxFile = MAX_PATH;
                ofn.lpstrFilter =
                           "Fichier source C\0*.c\0Fichier source CPP\0*.cpp\0";
                ofn.nFilterIndex = 1;
                ofn.Flags =
                       OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

Nous déclarons donc en premier lieu une variable du type de cette structure : ofn. Comme nous allons remplir seulement les champs qui nous intéressent, nous metterons la totalité de la structure à zéro. Ceci étant effectué en une seule opération par l'appel de la fonction ZeroMemory.
Ensuite, nous affectons son champ lpstrFile avec l'adresse d'un tableau de caractères que nous avons préalablement défini, pour y récupérer le nom et le chemin du fichier. Attention, il doit contenir une chaine valide avant l'appel de la fonction, car elle l'utilise aussi pour initialiser le champ d'édition : "Nom du fichier" de la boîte de dialogue.
Le champ lStructSize doit recevoir la taille de la structure.
Le champ hwndOwner le handle de la fenêtre parent.
Le champ nMaxFile la taille du tableau de caractère recevant le nom du fichier.
Le champ lpstrFilter avec l'adresse d'un tableau de caractère contenant des filtres de fichiers par leur extention. Les textes des filtres et les filtres étant séparés par des caractères null et le tableau ce terminant par deux caractères null (ce type de tableau de caractères est assez fréquent dans Windows).
Le champ nFilterIndex avec l'index du filtre que l'on désire afficher dans le champ d'édition "Fichier de type" à l'ouverture de la boîte de dialogue.
Le champ Flags avec différentes constantes qui influence sur son aspect ou ses fonctionnalités (voir l'aide API Win32).
Une fois les champs de la structure correctement rempli, nous appelons la fonction GetOpenFileName avec l'adresse de la structure précédement rempli comme paramètre. Si elle nous retourne la valeur TRUE, ce qui indique que l'utilisateur à fermé la boîte de dialogue avec le bouton "Ouvrir", nous pourons récupérer le nom et chemin du fichier sélectionné dans le buffer szFile afin de charger le fichier.

                if (GetOpenFileName(&ofn)==TRUE)
                 {
                    HANDLE hf;
                    DWORD FileSize,nbcharRead ;
                    CHAR *buffer;

                    hf = CreateFile(szFile, GENERIC_READ, 0,NULL,
                                    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
                    FileSize = GetFileSize(hf, NULL);
                    buffer = (PCHAR)LocalAlloc(LMEM_FIXED, FileSize+1);
                    ReadFile(hf, buffer, FileSize, &nbcharRead, NULL) ;
                    buffer[FileSize] = 0;
                    SendMessage(hEdit, WM_SETTEXT, 0, (LPARAM)buffer);
                    LocalFree(buffer);
                    CloseHandle(hf);
                 }

Connaissant maintenant le nom et le chemin du fichier, nous allons l'ouvrir avec la fonction de Windows CreateFile qui retourne un handle de fichier. Je vous invite à consuler l'aide API Win32 pour plus de détails sur cette fonction qui offre de nombreuses possibilités. Nous récupérons la taille du fichier avec la fonction GetFileSize. Nous créons un buffer de la taille du fichier + 1 octet avec la fonction de Windows LocalAlloc. Nous remplissons ce buffer avec le contenu du fichier à l'aide de la fonction ReadFile. Comme c'est un fichier texte nous ajoutons le zéro terminal au dernier élément du buffer pour en faire une chaîne de caractères.
Nous la copions dans le champs d'édition multi lignes, en lui envoyant un message WM_SETTEXT avec l'adresse du buffer comme dernier paramètre de SendMessage. Pour terminer, nous libérons la mémoire avec la fonction LocalFree et fermons le fichier avec la fonction CloseHandle.

Code complet :

#include <windows.h>

#define IDM_QUIT 1
#define IDM_OPEN 2

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG msg;
    WNDCLASS wc;
    HMENU hMenu, hSousMenu;

    hinst = hinstance;

    wc.style = 0 ;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

    hSousMenu = CreateMenu();
    AppendMenu(hSousMenu, MF_STRING, IDM_OPEN, "Ouvrir...");
    AppendMenu(hSousMenu, MF_SEPARATOR, 0, NULL);
    AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");
    hMenu  = CreateMenu();
    AppendMenu(hMenu,MF_POPUP,(UINT)hSousMenu,"Fichier");

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 640, 300,
                                                   NULL, hMenu, hinstance, NULL);
    if (!hwnd)  return FALSE;

    ShowWindow(hwnd, nCmdShow);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HWND hEdit;
    static BOOL EditNotChg = TRUE;
    switch (uMsg)
    {
        case WM_CREATE:
            {
             HFONT hFont;

             hEdit = CreateWindow("edit", "Texte", WS_CHILD | WS_VISIBLE |
                                   ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
                                           0, 0, 0, 0, hwnd, NULL, hinst, NULL);
             hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
             SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
             SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
                                                                MAKELONG(5, 5));
             return 0;
            }

        case WM_CLOSE:
            if(EditNotChg ||
               MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
                            ,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
                                                            DestroyWindow(hwnd);
            return 0;

        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_OPEN)
              {
                OPENFILENAME ofn;
                CHAR szFile[MAX_PATH]={0};

                ZeroMemory(&ofn, sizeof(OPENFILENAME));
                ofn.lStructSize = sizeof(OPENFILENAME);
                ofn.hwndOwner = hwnd;
                ofn.lpstrFile = szFile;
                ofn.nMaxFile = MAX_PATH;
                ofn.lpstrFilter =
                           "Fichier source C\0*.c\0Fichier source CPP\0*.cpp\0";
                ofn.nFilterIndex = 1;
                ofn.Flags =
                       OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

                if (GetOpenFileName(&ofn)==TRUE)
                 {
                    HANDLE hf;
                    DWORD FileSize,nbcharRead ;
                    CHAR *buffer;

                    hf = CreateFile(szFile, GENERIC_READ, 0,NULL,
                                    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
                    FileSize = GetFileSize(hf, NULL);
                    buffer = (PCHAR)LocalAlloc(LMEM_FIXED, FileSize+1);
                    ReadFile(hf, buffer, FileSize, &nbcharRead, NULL) ;
                    buffer[FileSize] = 0;
                    SendMessage(hEdit, WM_SETTEXT, 0, (LPARAM)buffer);
                    LocalFree(buffer);
                    CloseHandle(hf);
                 }
              }
            if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
            if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
            return 0;

        case WM_SIZE:
             MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
             return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

En annexe voici 3 autres boîtes de dialogue commune :
- La boîte de dialogue de sauvegarde.
- La boîte de dialogue de choix de police de caractères.
- La boîte de dialogue de choix de couleur.

J'ai testé les compilations avec C++ Builder 4 (Win 95), C++ Builder 6 (Win XP), et DevC++ (Win XP).

Remarques :

Compilé avec BCB6, la boîte de dialogue ne s'ouvrait pas si le programme était exécuté sous une plateforme Win95. Une erreur CDERR_STRUCTSIZE était retourné. Aprés une recherche sur le Web, j'ai trouvé que les compilations faite pour Win 2000 pouvait causer ce problème si les fichier header de Windows était recent. Je n'ai pas eu ce problème avec DevC++ avec une compilation sous Win XP.
La solution pour parrer à ce problème est d'intitaliser le champs lStructSize de la structure OPENFILENAME avec la constante : OPENFILENAME_SIZE_VERSION_400.

           ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
Voir une compilation conditionnelle :
#ifdef OPENFILENAME_SIZE_VERSION_400
                ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
#else
                ofn.lStructSize = sizeof(OPENFILENAME);
#endif

A tester selon vos configuration.

A vos PC. CGi

Avec la contribution de nico-pyright(c) pour la relecture.


Api Windows : Raccourcis clavier.

API Windows en C
5 - Les contextes de périphériques.

Introduction :

Nous allons voir dans ce nouveau chapitre "les sorties écrans", ou plutôt l'écriture sur les fenêtres. Sous Windows on parlera plutôt de dessin.
Windows a les facultés de pouvoir dessiner sur n'importe quel périphériques de sortie (Ecran, imprimante ...) sans se préoccuper de comment il fonctionne. Ce sont les drivers qui font le travail.
Pour dessiner sous Windows on doit obtenir du périphérique sur lequel l'on veut dessiner un contexte de périphérique (DC). Pour simplifier nous allons nous représenter le contexte de périphérique comme une surface de dessin que nous aura fourni la fenêtre sur laquelle l'on veut dessiner.

Dessin :

Nous allons reprendre l'exemple du chapitre 1. Nous y interceptons le message WM_KEYDOWN qui est envoyé à la procédure de fenêtre quand on appuie sur une touche et qu'elle possède le focus clavier. Pour une fenêtre (ou contrôle) avoir le focus clavier signifie qu'elle est la fenêtre active et que par conséquent, c'est elle qui reçoit les entrées clavier.

    switch (uMsg)
    {
        case WM_KEYDOWN :
            {
              int y;
              char st[] = "Bienvenue sur Developpez.com" ;
              HDC hdc = GetDC(hwnd);
              SetBkMode(hdc, TRANSPARENT);
              for(y=10; y <= 200; y += 20)
                                          TextOut(hdc, 10, y, st , lstrlen(st));
              ReleaseDC(hwnd, hdc);
              return 0;
            }

A la réception de ce message, nous écrirons 10 lignes avec la chaîne de caractères : "Bienvenue sur Developpez.com" décalées de 20 pixels verticalement les unes par rapport aux autres. Première opération, obtenir le handle d'un contexte de périphérique (HDC) pour la fenêtre en question à l'aide de la fonction GetDC qui reçoit comme paramètre de handle de la fenêtre sur laquelle l'on veux écrire ou dessiner. Quand on écrit ou dessine sous Windows, le fond est aussi peint par la même occasion. Comme il ne nous importe pas pour l'instant nous le mettrons transparent à l'aide de la fonction SetBkMode qui recevra donc comme paramètres le handle du contexte de périphérique et une constante de mode : TRANSPARENT. Nous pouvons alors écrire notre chaîne de caractères à l'aide de la fonction TextOut qui reçoit le handle du contexte de périphérique comme premier paramètre. Les deux suivants étant les coordonnées d'affichage (x,y). Le suivant étant un pointeur sur la chaîne de caractères à dessiner. Le dernier étant la longueur de la chaîne de caractères retournée par la fonction lstrlen (fonction de l'API Windows).
Nous devons libérer le contexte de périphérique après son utilisation. C'est le rôle de la fonction ReleaseDC.

Mais voilà, un problème se pose, à chaque fois que la fenêtre est cachée par une autre, le texte qui s'y trouve, disparaît. Ce qui est fort ennuyeux. Dans le cas présent vous pouvez bien le faire réapparaître en appuyant une autre fois sur une touche, mais ce n'est pas très pratique. Mais alors comment faire ? Et bien c'est encore un message qui intervient WM_PAINT :

Le message WM_PAINT :

A chaque fois qu'une fenêtre ou une partie de fenêtre à besoin de se redessiner Windows lui envoie un message WM_PAINT. Il nous suffira donc d'intercepter ce message dans sa procédure de fenêtre et d'appeler les fonctions de dessin suite à ce message:

        case WM_PAINT :
            {
              int y;
              char st[] = "Bienvenue sur Developpez.com" ;
              HDC hdc = GetDC(hwnd);
              SetBkMode(hdc, TRANSPARENT);
              for(y=10; y <= 200; y += 20)
                                          TextOut(hdc, 10, y, st , lstrlen(st));
              ReleaseDC(hwnd, hdc);
              return 0;
            }

Miracle ! Le texte ne disparaît plus.
Il est redessiné à chaque fois que cela est nécessaire.
Il subsiste un scintillement désagréable, surtout lors des redimensionnements. Ceci est du au fait que la zone client de la fenêtre est entièrement redessinée à chaque message WM_PAINT qu'elle reçoit. Une solution existe pour éliminer ce phénomène. C'est de redessiner seulement la partie de la fenêtre qui en à besoin. On l'appelle cette partie la zone invalide.
Pour cela, il faut obtenir le handle du contexte de périphérique avec la fonction BeginPaint au lieu de GetDC. Cette fonction nécessite un pointeur sur une variable préalablement défini de type (struct PAINTSTRUCT) comme paramètre afin d'y mémoriser des informations.

        case WM_PAINT :
            {
              int y;
              char st[] = "Bienvenue sur Developpez.com" ;
              PAINTSTRUCT ps;
              HDC hdc = BeginPaint(hwnd, &ps);
              SetBkMode(hdc, TRANSPARENT);
              for(y=10; y <= 200; y += 20)
                                          TextOut(hdc, 10, y, st , lstrlen(st));
              EndPaint(hwnd, &ps);
              return 0;
            }

Tout handle de contexte de périphérique obtenu avec BeginPaint doit être libéré avec EndPaint.
Remarque : Les fonctions BeginPaint et EndPaint ne peuvent être utilisées qu'exclusivement à la réception d'un message WM_PAINT.

Code complet :

#include <windows.h>

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG msg;
    WNDCLASS wc;

    wc.style = 0;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
                                                   NULL, NULL, hinstance, NULL);
    if (!hwnd) return FALSE;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        /* mis en commentaire et remplacé par WM_PAINT
        case WM_KEYDOWN :
            {
             int y;
              char st[] = "Bienvenue sur Developpez.com" ;
              HDC hdc = GetDC(hwnd);
              SetBkMode(hdc, TRANSPARENT);
              for(y=10; y <= 200; y += 20)
                                          TextOut(hdc, 10, y, st , lstrlen(st));
              ReleaseDC(hwnd, hdc);
              return 0;
            }      */

        /* mis en commentaire et remplacé par WM_PAINT suivant
        case WM_PAINT :
            {
              int y;
              char st[] = "Bienvenue sur Developpez.com" ;
              HDC hdc = GetDC(hwnd);
              SetBkMode(hdc, TRANSPARENT);
              for(y=10; y <= 200; y += 20)
                                          TextOut(hdc, 10, y, st , lstrlen(st));
              ReleaseDC(hwnd, hdc);
              return 0;
            }      */

        case WM_PAINT :
            {
              int y;
              char st[] = "Bienvenue sur Developpez.com" ;
              PAINTSTRUCT ps;
              HDC hdc = BeginPaint(hwnd, &ps);
              SetBkMode(hdc, TRANSPARENT);
              for(y=10; y <= 200; y += 20)
                                          TextOut(hdc, 10, y, st , lstrlen(st));
              EndPaint(hwnd, &ps);
              return 0;
            }

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

A vos PC. CGi

Avec la contribution de nico-pyright(c) pour la relecture.


Api Windows : Raccourcis clavier.

API Windows en C
6 - Dessiner.

Introduction :

Après avoir vu les contextes de périphériques, nous allons aborder les principales fonctions de dessin.

Comme vu au chapitre 5 nous dessinerons suite à la réception du message WM_PAINT dans un contexte de périphérique obtenu avec BeginPaint.

Dessin :

Nous allons continuer sur la base de l'exemple du chapitre précédent. Nous y écrirons tout d'abord la même chaîne de caractère avec TextOut, mais une seule fois. Ensuite, nous tracerons un rectangle au dessous de la chaîne de caractères :

              Rectangle(hdc, 20, 60, 120, 100);

Cette fonction reçoit comme paramètres le handle de contexte de périphérique et les coordonnées des points haut gauche et bas droit du rectangle. Windows pour dessiner ce rectangle utilise un crayon et un pinceau. Quand vous obtenez un handle de contexte de périphérique, il possède déjà un crayon et un pinceau. Mais vous pouvez aussi dessiner avec des crayons et des pinceaux que vous avez créés.

              HBRUSH hbRed, hbrOld;
              HPEN hp2px, hpOld;

              /*...   ...*/

              hp2px = CreatePen(PS_SOLID, 2, 0x00FF0000);
              hpOld = SelectObject(hdc,hp2px);

              hbRed = CreateSolidBrush(0x000000FF);
              hbrOld = SelectObject(hdc,hbRed);

              Rectangle(hdc, 160, 60, 260, 100);

              SelectObject(hdc,hbrOld);
              DeleteObject(hbRed);

              SelectObject(hdc,hpOld);
              DeleteObject(hp2px);

La création d'un crayon ce fait avec la fonction CreatePen qui reçoit comme paramètres une constante indiquant le type de trait (continu dans l'exemple), ensuite la largeur du trait (2 pixels) et enfin sa couleur (bleu). Après son appel, elle retourne un handle de crayon.
Le pinceau rouge est créé avec la fonction CreateSolidBrush qui reçoit comme paramètre une couleur. Mais pour pouvoir les utiliser avec un contexte de périphérique, il faut les sélectionner. C'est le rôle de la fonction SelectObject, qui reçoit le handle du contexte de périphérique sur lequel on veut utiliser l'objet (pinceau, crayon ...) et le handle de l'objet comme second paramètre. Le second rectangle est donc dessiné avec un contour tracé avec le crayon bleu d'une largeur de deux pixels et son intérieur peint avec le pinceau rouge.
Quand vous en avez terminé avec l'utilisation des objets que vous avez créé, il faut les détruire avec la fonction DeleteObject. De même avant de libérer le contexte de périphérique il faut toujours sélectionner ses objets d'origine avec SelectObject. Le handle de ces objets étant retourné par la fonction SelectObject.

Pour la forme nous dessinerons ensuite un cercle à l'aide le la fonction Ellipse et d'un pinceau à hachures diagonales croisés, puis un trait bleu à l'aide des fonctions MoveToEx qui positionne le point de départ et LineTo qui trace jusqu'au point d'arrivée quelle reçoit comme paramètre et qui devient le nouveau point de départ.
Nous n'avons vu que quelques fonctions de dessin, veuillez consulter l'aide API Win32 pour plus de détail.

              hbGreen = CreateHatchBrush(HS_DIAGCROSS, 0x0000BB00);
              SelectObject(hdc,hbGreen);
              DeleteObject(hbRed);

              Ellipse(hdc, 300, 20, 400, 120);

              MoveToEx(hdc, 20, 140, NULL);
              LineTo(hdc, 400, 140);

Je vous invite de même à consulter l'aide Win32 API pour voir toutes les possibilités que fournissent les crayons et les pinceaux.

Les polices de caractères :

Nous avons dessiné une chaîne de caractère sur la fenêtre, mais comme vous le savez Windows peut utiliser d'autres polices de caractères installées sur le système tel les polices "True Type".
Pour les utiliser le principe est semblable aux crayons ou pinceaux. Il faut créer l'objet police avec CreateFont ou CreateFontIndirect qui retournent un handle de police. Dans l'exemple nous utiliserons CreateFontIndirect car elle est plus simple à utiliser. CreateFontIndirect utilise une structure LOGFONT pour initialiser la police. Nous remplirons seulement deux de ses champs dans l'exemple pour qu'il reste simple. Les autres serons initialisé à zéro à l'aide de la fonction ZeroMemory :

              HFONT NewFont, OldFont;
              LOGFONT lf;

              /*...   ...*/

              ZeroMemory(&lf, sizeof(LOGFONT));
              lstrcpy(lf.lfFaceName,"Times New Roman");
              lf.lfHeight = 36;

              NewFont = CreateFontIndirect(&lf);
              OldFont = SelectObject(hdc,NewFont);

              SetTextColor(hdc, 0x000000FF);
              TextOut(hdc, 10, 180, st, lstrlen(st));

              SelectObject(hdc,OldFont);
              DeleteObject(NewFont);

Première opération, remplir les champs de la variable lf de type LOGFONT. Elle possède un champ lfFaceName : tableau de caractères où nous copions le nom de la police que nous désirons utiliser avec la fonction lstrcpy. Ce tableau a une taille de 32 caractères (y compris le zéro terminal). Nous mettons le champ lfHeight correspondant à la hauteur des caractères à 36. Nous positionnerons le champ lfItalic à true et le champ lfWeight à FW_BOLD pour écrire la chaîne de caractères en gras italique.
Tout comme pour les crayons ou pinceaux, il faut sélectionner les objets Fonts pour les utiliser et les détruire après utilisation, sans oublier de sélectionner la police d'origine avant de libérer le contexte de périphérique.
Pour plus de précisions sur les polices de caractères, veuillez consulter l'aide API Win32.

Code complet :

#include <windows.h>

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG msg;
    WNDCLASS wc;

    wc.style = 0;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

    hwnd = CreateWindow("MaWinClass", "Dessiner", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 460, 300,
                                                   NULL, NULL, hinstance, NULL);
    if (!hwnd) return FALSE;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_PAINT :
            {
              HBRUSH hbRed, hbGreen, hbrOld;
              HPEN hp2px, hpOld;
              HFONT NewFont, OldFont;
              LOGFONT lf;

              char st[] = "Bienvenue sur Developpez.com" ;

              PAINTSTRUCT ps;
              HDC hdc = BeginPaint(hwnd, &ps);
              SetBkMode(hdc, TRANSPARENT);

              TextOut(hdc, 10, 20, st, lstrlen(st));

              Rectangle(hdc, 20, 60, 120, 100);

              hp2px = CreatePen(PS_SOLID, 2, 0x00FF0000);
              hpOld = SelectObject(hdc,hp2px);

              hbRed = CreateSolidBrush(0x000000FF);
              hbrOld = SelectObject(hdc,hbRed);

              Rectangle(hdc, 160, 60, 260, 100);

              hbGreen = CreateHatchBrush(HS_DIAGCROSS, 0x0000BB00);
              SelectObject(hdc,hbGreen);
              DeleteObject(hbRed);

              Ellipse(hdc, 300, 20, 400, 120);

              MoveToEx(hdc, 20, 140, NULL);
              LineTo(hdc, 400, 140);

              SelectObject(hdc,hbrOld);
              DeleteObject(hbGreen);

              SelectObject(hdc,hpOld);
              DeleteObject(hp2px);

              ZeroMemory(&lf, sizeof(LOGFONT));
              lstrcpy(lf.lfFaceName,"Times New Roman");
              lf.lfHeight = 36;
              lf.lfItalic = TRUE;
              lf.lfWeight = FW_BOLD;

              NewFont = CreateFontIndirect(&lf);
              OldFont = SelectObject(hdc,NewFont);

              SetTextColor(hdc, 0x000000FF);
              TextOut(hdc, 10, 180, st, lstrlen(st));

              SelectObject(hdc,OldFont);
              DeleteObject(NewFont);

              EndPaint(hwnd, &ps);
              return 0;
            }

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

A vos PC. CGi

Avec la contribution de nico-pyright(c) pour la relecture.


Api Windows : Raccourcis clavier.

API Windows en C
7 - Les ressources.

Introduction :

Dans ce chapitre nous allons aborder les ressources.
Tout d'abord qu'appelle t'on les ressources dans un programme Windows ?
Ce sont des données de toutes natures qui sont incorporées à l'exécutable. Cela peut être des images, des icônes, des chaînes de caractères, des blocs de données de toutes sortes... Mais aussi des boîtes de dialogue et des menus qui seront construits à partir de script.

Incorporation des ressources à l'exécutable :

L'incorporation des ressources à l'exécutable est réalisée par le linker. Nous devons lui fournir un fichier contenant les ressources. Ces fichiers ont l'extension ".res". Ils sont compilés par un compilateur de ressources, auquel on fourni un fichier de script de ressources. Les fichiers de script de ressources sont des fichiers texte ayant l'extension ".rc". Il existe aussi des éditeurs de ressources permettant de créer visuellement les fichiers ressources.
Ce tutoriel n'étant pas déstiné à des outils particuliés, nous utiliserons uniquement les scripts de ressources dans les exemples qui vont suivre.
Si vous utilisez un EDI, il suffit d'ajouter les scripts de ressources au projet (pour Dev-C++ il faut les ajouter par le menu "Projects" puis "Projects Options" puis par le bouton "Load Resource", pour C++ Builder il faut tout simplement ajouter les fichiers au projet). La compilation des ressources sera dans ce cas transparente.
Nous allons en voir deux dans ce chapitre : les icônes et les menus.

Le script de ressources :

Nous allons rependre l'exemple du chapitre 3 - Les commandes et notifications. Nous allons d'abord ajouter des icônes dans les ressources de l'exécutable. Créons tout d'abord un fichier script de ressources nommé "resource.rc" avec le texte suivant :

1 ICON icone.ico
2 ICON autre.ico

Chaque ligne est composée de trois éléments, le premier est un identificateur, le second le type de ressource (ICON pour une icône dans l'exemple) le dernier est le contenu, dans le cas de l'exemple ce sera le contenu d'un fichier. Ils doivent bien sur exister. Les ayant mit dans le même dossier que les fichiers sources du programme, je n'ai pas indiqué le chemin pour les localiser. S'ils se trouvent ailleurs il faudra bien sur mettre le chemin (j'ai du tout de même le mettre pour la compilation avec Dev-C++ malgré qu'ils se trouvaient dans le même dossier).
Certain compilateur de resources demande d'inclure les fichiers entêtes de Windows qui contiennent la définition des identificateurs utilisés dans le fichier script de ressources.
Il peux y avoir des différences notables entre les différents compilateurs de ressources. Je vous invite à consulter l'aide de celui que vous utilisez si vous rencontrez des problèmes de compilation.
Je le répète une dernière fois, ce fichier devra être ajouté au projet si vous possédez un EDI ou compilé à l'aide d'un compilateur de ressources et lié à l'exécutable si vous n'avez pas d'EDI.
A ce stade, si vous compilez et que vous visualisez le fichier exécutable dans l'explorateur de fichiers, il devrait avoir la première icône de la liste (ce n'est pas forcement vrai si vous utilisez un EDI, ils peuvent ajouter automatiquement une icône aux ressources de l'exécutable).
Nous allons maintenant mettre la deuxième icône comme icône de la fenêtre (celle qui ce trouve à gauche sur la barre de titre) :

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASS wc;

    wc.style = 0 ;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = NULL;
    wc.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(2));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName =  "MyMenu";
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

Pour cela on affecte le champ hIcon de la classe de fenêtre avec une icône que l'on charge avec la fonction LoadIcon. Elle reçoit comme paramètre le handle d'instance de l'application, pour son second paramètre elle attend un pointeur sur une chaîne de caractères identifiant l'icône. Dans le script de ressource nous l'avons identifié avec une valeur numérique. C'est avec la macro MAKEINTRESOURCE que nous faions cette conversion.
Nous aurions pu les identifier avec des valeurs littérales :

ICONE1 ICON icone.ico
ICONE2 ICON autre.ico

Dans ce cas LoadIcon aurait reçu un pointeur sur la chaîne de caractère identifiant l'icône dans son second paramètre.

     wc.hIcon = LoadIcon(hinstance,"ICONE2");

Ressources de type menu :

Toujours avec le même exemple, nous allons supprimer le menu que l'on avait créé par programme au chapitre 3 et le créer avec un script de ressources :

LEMENU MENU
BEGIN
  POPUP "Fichier"
    BEGIN
       MENUITEM "&Nouveau", IDM_NEW
       MENUITEM SEPARATOR
       MENUITEM "&Quitter", IDM_QUIT
    END
  POPUP "Aide"
    BEGIN
       MENUITEM "A propos...", IDM_ABOUT
    END
END

Nous retrouvons là aussi nos trois éléments : l'identificateur de la ressource (littérale dans cet exemple), suivi de son type (MENU : barre de menu) puis de son contenu. Ici le contenu n'est pas un fichier, mais un script de construction de barre de menu, balisé par les mots BEGIN et END. Elle possède deux sous menu (POPUP : menu déroulant) "fichier" et "Aide". Le premier possède trois options (MENUITEM) "&Nouveau", SEPARATOR et "&Quitter". La liste des options d'un sous menu est aussi balisée par les mots BEGIN et END. Chaque option doit être identifiée par une constante numérique. Cette constante sera envoyée à la procédure de fenêtre quand l'option du menu sera actionnée. Il est préférable de les nommer pour plus de clarté dans le code. Comme elles devront être connues du script de ressource et du fichier source du programme, nous les définissons dans un fichier entête séparé, nommé dans cet exemple "resource.h" :

#define IDM_QUIT  1
#define IDM_NEW   2
#define IDM_ABOUT 3

Ce fichier devra donc être inclut dans le fichier source et dans le fichier script de ressource (voir code à la fin de ce document). Le menu sera ensuite affecté à la classe de fenêtre. Ceci en affectant au champ lpszMenuName de la classe de fenêtre, un pointeur sur une chaîne de caractères contenant le nom litéral du menu :

    wc.lpszMenuName =  "LEMENU";

C'est tout pour sa construction. La récupération des actions du menu ce fait sur réception des messages WM_COMMAND de la même manière qu'au chapitre 3 :

    switch (uMsg)
    {
        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
            /*...         */

Vous avez du remarquer que j'ai mis le caractère '&' dans le texte des options de menu "&Nouveau" et "&Quitter". Ce signe sert à souligner le caractère qui le suit et à donner la possibilité d'activer l'option correspondante avec la touche du clavier correspondant à ce caractère. Ceci uniquement quand le popup menu est ouvert. A ne pas confondre avec les accélérateurs clavier que l'on verra lors d'un chapitre ultérieur.
Nous aurons l'occasion de revenir sur les différents types de ressources au cours des chapitres suivants.

Code complet :

resource.h :

#define IDM_QUIT  1
#define IDM_NEW   2
#define IDM_ABOUT 3

resource.rc :

#include <windows.h>

#include "resource.h"

1 ICON icone.ico
2 ICON autre.ico

LEMENU MENU
BEGIN
  POPUP "Fichier"
    BEGIN
       MENUITEM "&Nouveau", IDM_NEW
       MENUITEM SEPARATOR
       MENUITEM "&Quitter", IDM_QUIT
    END
  POPUP "Aide"
    BEGIN
       MENUITEM "A propos...", IDM_ABOUT
    END
END

winmain.c :

#include <windows.h>

#include "resource.h"

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG msg;
    WNDCLASS wc;

    hinst = hinstance;

    wc.style = 0 ;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(2));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName =  "LEMENU";
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
                                                   NULL, NULL, hinstance, NULL);
    if (!hwnd)  return FALSE;

    ShowWindow(hwnd, nCmdShow);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HWND hEdit;
    static BOOL EditNotChg = TRUE;
    switch (uMsg)
    {
        case WM_CREATE:
            {
             HFONT hFont;
             hEdit = CreateWindow("edit", "Texte",
               WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
                                            0, 0, 0, 0, hwnd, NULL, hinst, NULL);
             hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
             SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
             SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
                                                                MAKELONG(5, 5));
             return 0;
            }

        case WM_CLOSE:
            if(EditNotChg ||
               MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
                            ,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
                                                            DestroyWindow(hwnd);
            return 0;

        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);

            if(LOWORD(wParam) == IDM_NEW)
                if(EditNotChg ||
                  MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer votre travail ?"
                            ,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
                      {
                              SendMessage(hEdit,WM_SETTEXT,0,(long)"");
                              EditNotChg = TRUE;
                      }

            if(LOWORD(wParam) == IDM_ABOUT)
                     MessageBox(hwnd,"Mon beau programme !","A propos !",MB_OK);

            if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;

            return 0;

        case WM_SIZE:
             MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
             return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

J'ai testé les compilations avec C++ Builder et DevC++.

A vos PC. CGi

Avec la contribution de nico-pyright(c) pour la relecture.


Api Windows : Raccourcis clavier.

API Windows en C
8 - Les boîtes de dialogue personnalisées.

Introduction :

Dans ce chapitre nous allons voir comment créer et appeler une boîte de dialogue personnalisée. Nous continuerons avec l'exemple du chapitre précédent. Nous remplacerons la boîte de message appelée par le menu "A propos..." par une boîte de dialogue personnalisée contenant un bouton, une icône et un texte.

Le script de ressources :

Les boîtes de dialogue personnalisées sont créées à partir de ressources :

DIALOG1 DIALOG
     60, 60, 160, 80
           STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
                                                             CAPTION "A propos"
BEGIN
    DEFPUSHBUTTON "Ok", IDOK, 56, 50, 42, 12
    ICON 2, -1, 20, 15, 32, 32
    LTEXT "Mon beau programme !", -1, 60, 18, 80, 10
END

La ressource est composée de son identificateur littéral, suivi de son type (DIALOG), suivie de ses propriétés (position, dimensions, style et titre), suivi de son contenu balisé par les mots BEGIN et END. Il est en général constitué de contrôles. Chaque contrôle est décrit par son type, suivi de son identification visuelle (texte pour le bouton et le contrôle texte, identificateur de ressource pour l'icône), suivi de sa constante numérique d'identification, puis de sa position dans la boîte de dialogue et enfin de ses dimensions (les largeurs et hauteurs des contrôles ne sont pas en pixel, mais dépendent de la police de caractères utilisée). L'icône et le le contrôle texte ont un identificateur à -1 car il ne sont là que pour la décoration, mais il faut tout de même leur mettre un identificateur. Celui du bouton est à IDOK que nous n'avons pourtant pas défini. IDOK est défini dans Windows, il est envoyé à la procédure de fenêtre de la boîte de dialogue quand on appuie sur la touche "Entrée". Est défini aussi l'identificateur IDCANCEL qui lui est envoyé quand on appuie sur la touche "Echap" ou que l'on tente de fermer la boîte de dialogue.

Appel de la boîte de dialogue :

La boîte de dialogue étant maintenant dans les ressources, nous pouvons l'appeler afin de l'ouvrir. C'est le rôle de la fonction DialogBox. Son premier paramètre est le handle d'instance de l'application, le second, un pointeur sur la chaîne de caractères identifiant la ressource, le troisième est le handle de la fenêtre parent et le dernier un pointeur sur sa procédure de fenêtre.

    switch (uMsg)
    {
        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_ABOUT)
                       DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc);

Procédure de fenêtre de la boîte de dialogue :

Comme pour la procédure de fenêtre de la fenêtre principale c'est à vous de la définir. C'est Windows qui l'appellera quand elle aura un message.

LRESULT CALLBACK Dialog1Proc(HWND, UINT, WPARAM, LPARAM);

BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    switch (uMsg)
    {
      case WM_INITDIALOG:

         return TRUE;

      case WM_COMMAND:
         if (LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK)
                {
                   EndDialog(hDlg,0);
                   return TRUE;
                }

      default:
         return FALSE;
    }
}
Comme vous pouvez le constater elle est fort semblable à celle de la fenêtre principale. Si le message est traité, elle doit renvoyer TRUE sinon elle doit renvoyer FALSE. WM_INITDIALOG doit être intercepté et renvoyer TRUE afin que les commandes IDOK et IDCANCEL soit envoyées lors des appuis sur les touches correspondantes. Le message WM_INITDIALOG est envoyé par Windows après la création de la boîte de dialogue, mais avant qu'elle soit visible. Ceci afin de permettre d'initialiser les contrôles qu'elle contient.
Dans l'exemple nous interceptons seulement les commandes IDOK et IDCANCEL afin de la fermer par l'appel de la fonction EndDialog.

Code complet :

resource.h :

#define IDM_QUIT  1
#define IDM_NEW   2
#define IDM_ABOUT 3

resource.rc :

#include <windows.h>

#include "resource.h"

1 ICON icone.ico
2 ICON autre.ico

LEMENU MENU
BEGIN
  POPUP "Fichier"
    BEGIN
       MENUITEM "Nouveau", IDM_NEW
       MENUITEM SEPARATOR
       MENUITEM "Quitter", IDM_QUIT
    END
  POPUP "Aide"
    BEGIN
       MENUITEM "A propos...", IDM_ABOUT
    END
END

DIALOG1 DIALOG
  60, 60, 160, 80
       STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
             CAPTION "A propos"
BEGIN
    DEFPUSHBUTTON "Ok", IDOK, 56, 50, 42, 12
    ICON 2, -1, 20, 15, 32, 32
    LTEXT "Mon beau programme !", -1, 60, 18, 80, 10
END

winmain.c :

#include <windows.h>

#include "resource.h"

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG msg;
    WNDCLASS wc;

    hinst = hinstance;

    wc.style = 0 ;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(2));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName =  "LEMENU";
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
                                                   NULL, NULL, hinstance, NULL);
    if (!hwnd)  return FALSE;

    ShowWindow(hwnd, nCmdShow);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HWND hEdit;
    static BOOL EditNotChg = TRUE;
    switch (uMsg)
    {
        case WM_CREATE:
            {
             HFONT hFont;
             hEdit = CreateWindow("edit", "Texte",
               WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
                                            0, 0, 0, 0, hwnd, NULL, hinst, NULL);
             hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
             SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
             SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
                                                                MAKELONG(5, 5));
             return 0;
            }

        case WM_CLOSE:
            if(EditNotChg ||
               MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
                            ,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
                                                            DestroyWindow(hwnd);
            return 0;

        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);

            if(LOWORD(wParam) == IDM_NEW)
                if(EditNotChg ||
                  MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer votre travail ?"
                            ,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
                      {
                              SendMessage(hEdit,WM_SETTEXT,0,(long)"");
                              EditNotChg = TRUE;
                      }

            if(LOWORD(wParam) == IDM_ABOUT)
                       DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc);

            if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;

            return 0;

        case WM_SIZE:
             MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
             return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}
/******************************************************************************/

BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    switch (uMsg)
    {
      case WM_INITDIALOG:

         return TRUE;

      case WM_COMMAND:
         if (LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK)
                {
                   EndDialog(hDlg,0);
                   return TRUE;
                }

      default:
         return FALSE;
    }
}

J'ai testé les compilations avec C++ Builder et DevC++. Le compilateur de ressources de DevC++ a été défaillant pour l'affichage de l'icône dans la boîte de dialogue.

A vos PC.

CGi

Avec la contribution de nico-pyright(c) et pharaonix pour la relecture.


Api Windows : Raccourcis clavier.

API Windows en C
9 - Communiquer avec les boîtes de dialogue.

Introduction :

Maintenant que nous savons créer une boîte de dialogue, nous allons voir comment initialiser ses contrôles et comment restituer leur contenu à la fenêtre qui a appelé la boîte de dialogue. Nous continuons toujours avec le même exemple où nous ajouterons une option au menu nommé "Paramètres...". Elle appellera une nouvelle boîte de dialogue qui comportera un champ de saisie (EDITTEXT), un bouton "Ok" et un bouton "Annuler". A l'ouverture de la boîte de dialogue, le champ de saisie devra être initialisé avec le titre de la fenêtre principale. A sa fermeture, le titre de la fenêtre principale devra être modifié si la boîte de dialogue est fermée avec le bouton "Ok".

Le script de ressources :

Le script de ressources est fort semblable à celui de la boîte de dialogue du chapitre précédent :

DIALOG2 DIALOG
    60, 60, 182, 70
          STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
                                                         CAPTION "Paramètres"
BEGIN
    DEFPUSHBUTTON "OK", IDOK, 36, 42, 42, 12
    PUSHBUTTON "Cancel", IDCANCEL, 96, 42, 42, 12
    EDITTEXT IDE_EDIT1, 88, 15, 74, 12
    LTEXT "Titre de la fenêtre", -1, 24, 18, 60, 10
END

On peut remarquer deux types de boutons "DEFPUSHBUTTON" et "PUSHBUTTON". Le bouton qui à le type "DEFPUSHBUTTON" est le bouton par défaut. Le contrôle d'édition "EDITTEXT" est identifié par la constante IDE_EDIT1 définie dans le fichier resource.h.

Appel de la boîte de dialogue :

Nous allons appeler la boîte de dialogue avec la fonction DialogBoxParam. Cette fonction permet de passer un paramètre supplémentaire de notre choix. Il pourra être récupéré dans sa procédure de fenêtre.
Dans l'exemple, nous lui passerons le handle de la fenêtre qui l'appelle.

    switch (uMsg)
    {
        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_PARAM)
                       DialogBoxParam(hinst, "DIALOG2" , hwnd,
                                            (DLGPROC)Dialog2Proc, (LPARAM)hwnd);

Procédure de fenêtre de la boîte de dialogue :

Récupération des données venant de la fenêtre principale :

BOOL APIENTRY Dialog2Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    static HWND hParent;
    switch (uMsg)
    {
      case WM_INITDIALOG:
          {
            CHAR st[256];
            hParent = (HWND)lParam;
            GetWindowText(hParent, st, 256);
            SetDlgItemText(hDlg, IDE_EDIT1, st);
          }
         return TRUE;

A la réception du message WM_INITDIALOG nous récupérons le titre de la fenêtre qui l'a appelée avec GetWindowText. Son handle est récupéré dans la variable lParam transmise avec WM_INITDIALOG. C'est le paramètre supplémentaire transmit par la fonction DialogBoxParam. Nous affectons ce titre au champ d'édition avec la fonction SetDlgItemText. Elle reçoit comme paramètres le handle de la boîte de dialogue, puis la constante identifiant le contrôle d'édition, puis le pointeur sur la chaîne de caractères.
Maintenant que notre boîte de dialogue est ouverte, nous pouvons modifier le champ de saisie, puis la fermer.

Si nous la fermons avec le bouton "Ok" :

    switch (uMsg)
    {
      case WM_COMMAND:
         if (LOWORD(wParam) == IDOK )
                {
                   CHAR st[256];
                   GetDlgItemText(hDlg, IDE_EDIT1, st, 256);
                   SetWindowText(hParent,st);
                   EndDialog(hDlg,0);
                   return TRUE;
                }

Nous faisons l'opération inverse. Nous récupérons le contenu du champ d'édition avec la fonction GetDlgItemText et nous l'affectons à la fenêtre principale dont on avait mémorisé le handle dans la variable hParent.
Si vous m'avez suivi jusque là, vous vous rendez compte qu'il y a plusieurs possibilités pour transmettre les données entres fenêtres. Celle-ci n'était peut-être pas la meilleure dans cet exemple. On aurait pu utiliser des variables globales ou transmettre un pointeur sur structure dans le paramètre supplémentaire de DialogBoxParam...

Styles étendus des contrôles :

Vous avez remarqué que le contrôle d'édition dans notre boîte de dialogue a un aspect 3D et pas celui qui ce trouve sur la fenêtre principale. Pour donner un aspect 3D à un contrôle qui se trouve sur une fenêtre qui n'est pas une boîte de dialogue, il faut lui donner le style étendu WS_EX_CLIENTEDGE. Pour cela il faut créer le contrôle avec la fonction CreateWindowEx. Les styles étendus sont donnés dans le premier paramètre de CreateWindowEx. Les autres paramètres de cette fonction sont les mêmes que ceux de CreateWindow.

             hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "edit", "Texte",
               WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
                                            0, 0, 0, 0, hwnd, NULL, hinst, NULL);

Code complet :

resource.h :

#define IDM_QUIT  1
#define IDM_NEW   2
#define IDM_ABOUT 3
#define IDM_PARAM 4

#define IDE_EDIT1 101

resource.rc :

#include <windows.h>

#include "resource.h"

1 ICON icone.ico
2 ICON autre.ico

LEMENU MENU
BEGIN
  POPUP "Fichier"
    BEGIN
       MENUITEM "Nouveau", IDM_NEW
       MENUITEM "Paramètres...", IDM_PARAM
       MENUITEM SEPARATOR
       MENUITEM "Quitter", IDM_QUIT
    END
  POPUP "Aide"
    BEGIN
       MENUITEM "A propos...", IDM_ABOUT
    END
END

DIALOG2 DIALOG
    60, 60, 182, 70
          STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
                                                         CAPTION "Paramètres"
BEGIN
    DEFPUSHBUTTON "OK", IDOK, 36, 42, 42, 12
    PUSHBUTTON "Cancel", IDCANCEL, 96, 42, 42, 12
    EDITTEXT IDE_EDIT1, 88, 15, 74, 12
    LTEXT "Titre de la fenêtre", -1, 24, 18, 60, 10
END

DIALOG1 DIALOG
   60, 60, 160, 80
          STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
                                                         CAPTION "A propos"
BEGIN
    DEFPUSHBUTTON "Ok", IDOK, 56, 50, 42, 12
    ICON 2, -1, 20, 15, 32, 32
    LTEXT "Mon beau programme !", -1, 60, 18, 80, 10
END

winmain.c :

#include <windows.h>

#include "resource.h"

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog2Proc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG msg;
    WNDCLASS wc;

    hinst = hinstance;

    wc.style = 0 ;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(2));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName =  "LEMENU";
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
                                                   NULL, NULL, hinstance, NULL);
    if (!hwnd)  return FALSE;

    ShowWindow(hwnd, nCmdShow);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HWND hEdit;
    static BOOL EditNotChg = TRUE;
    switch (uMsg)
    {
        case WM_CREATE:
            {
             HFONT hFont;
             hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "edit", "Texte",
               WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
                                            0, 0, 0, 0, hwnd, NULL, hinst, NULL);
             hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
             SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
             SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
                                                                MAKELONG(5, 5));
             return 0;
            }

        case WM_CLOSE:
            if(EditNotChg ||
               MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
                            ,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
                                                            DestroyWindow(hwnd);
            return 0;

        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);

            if(LOWORD(wParam) == IDM_NEW)
                if(EditNotChg ||
                  MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer votre travail ?"
                            ,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
                      {
                              SendMessage(hEdit,WM_SETTEXT,0,(LPARAM)"");
                              EditNotChg = TRUE;
                      }

            if(LOWORD(wParam) == IDM_ABOUT)
                       DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc);

            if(LOWORD(wParam) == IDM_PARAM)
                       DialogBoxParam(hinst, "DIALOG2" , hwnd,
                                            (DLGPROC)Dialog2Proc, (LPARAM)hwnd);

            if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;

            return 0;

        case WM_SIZE:
             MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
             return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}
/******************************************************************************/

BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    switch (uMsg)
    {
      case WM_INITDIALOG:

         return TRUE;

      case WM_COMMAND:
         if (LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK)
                {
                   EndDialog(hDlg,0);
                   return TRUE;
                }

      default:
         return FALSE;
    }
}
/******************************************************************************/

BOOL APIENTRY Dialog2Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    static HWND hParent;
    switch (uMsg)
    {
      case WM_INITDIALOG:
          {
            CHAR st[256];
            hParent = (HWND)lParam;
            GetWindowText(hParent, st, 256);
            SetDlgItemText(hDlg, IDE_EDIT1, st);
          }
         return TRUE;

      case WM_COMMAND:
         if (LOWORD(wParam) == IDOK )
                {
                   CHAR st[256];
                   GetDlgItemText(hDlg, IDE_EDIT1, st, 256);
                   SetWindowText(hParent,st);
                   EndDialog(hDlg,0);
                   return TRUE;
                }
         if (LOWORD(wParam) == IDCANCEL )
                {
                   EndDialog(hDlg,0);
                   return TRUE;
                }
      default:
         return FALSE;
    }
}

J'ai testé les compilations avec C++ Builder et DevC++.
Voici un exemple semblable avec le contrôle case à cocher : Le contrôle CheckBox ou case à cocher.

A vos PC. CGi

Avec la contribution de pharaonix pour la relecture.


Api Windows : Raccourcis clavier.

API Windows en C
10 - Les accélérateurs clavier.

Introduction :

Les accélérateurs clavier sont des raccourcis clavier qui servent à lancer des actions. Actions qui la plus part du temps peuvent aussi se lancer depuis le menu.
Nous allons continuer sur la base de l'exemple du chapitre précédent. Nous affecterons un raccourcis clavier aux commandes des options suivantes du menu : Nouveau, Paramètres, A propos. Quitter à toujours un raccourci clavier qui est "Alt F4" :

Le script de ressources :

Les accélérateurs clavier sont définis dans les ressources sous forme d'une table :

LesAccel ACCELERATORS
BEGIN
    "N", IDM_NEW, CONTROL, VIRTKEY
    "P", IDM_PARAM, CONTROL, VIRTKEY
    VK_F1, IDM_ABOUT, VIRTKEY
END

Le premier élément est le nom littéral de la ressource, le deuxième son type (ACCELERATORS), suivi de son contenu balisé par BEGIN et END. Le contenu est la table de raccourcis clavier proprement dite où chaque raccourci est décrit par la touche concernée décrite sous forme de chaîne de caractère ou de code de touche virtuel, suivie de la constante d'identification de l'action. Ici les constantes sont les mêmes que les options de menu que l'on veut shunter, suivient de ou des touches étendues : CONTROL, ALT , SHIFT. Et de VIRTKEY ou ASCII.

Chargement des tables :

Pour être fonctionnelles, les tables de raccourcis clavier doivent être chargées par l'application :

      HACCEL haccel;

      haccel = LoadAccelerators(hinstance, "LesAccel");

C'est le rôle de la fonction LoadAccelerators qui reçoit comme paramètres le handle d'instance de l'application suivie du nom de la table. Celui que l'on lui avait donné dans le script de ressources. La fonction nous retourne ensuite un handle de table d'accélérateurs.

Comme le clavier nous envoie que des messages d'appui ou de relâchement de touche, nous devons donc traduire ces messages en commande pour les touches faisant partie de la table d'accélérateurs. C'est le rôle de la fonction TranslateAccelerator. Elle reçoit comme paramètres, le handle de la fenêtre, le handle de la table d'accélérateurs et l'adresse du message à traiter. Elle s'utilise dans la boucle de message :

    while (GetMessage(&msg, NULL, 0, 0))
    {
      if (!TranslateAccelerator(hwnd, haccel, &msg))
          {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
          }
    }

Quand TranslateAccelerator traite un message WM_KEYDOWN, elle vérifie si le code de la touche se trouve dans la table. Si c'est le cas, elle envoie un message WM_COMMAND à la procédure de fenêtre avec la commande correspondante. Dans ce cas, elle retourne alors la valeur TRUE, que l'on testera afin de ne pas traiter le message WM_KEYDOWN.
Il est d'usage d'indiquer les raccourcis clavier sur le texte des options de menu :

LEMENU MENU
BEGIN
  POPUP "Fichier"
    BEGIN
       MENUITEM "Nouveau\tCtrl+N", IDM_NEW
       MENUITEM "Paramètres...\tCtrl+P", IDM_PARAM
       MENUITEM SEPARATOR
       MENUITEM "Quitter\tAlt+F4", IDM_QUIT
    END
  POPUP "Aide"
    BEGIN
       MENUITEM "A propos...", IDM_ABOUT
    END
END

Code complet :

resource.h :

#define IDM_QUIT  1
#define IDM_NEW   2
#define IDM_ABOUT 3
#define IDM_PARAM 4

#define IDE_EDIT1 101

resource.rc :

#include <windows.h>

#include "resource.h"

1 ICON chip.ico
2 ICON factory.ico

LEMENU MENU
BEGIN
  POPUP "Fichier"
    BEGIN
       MENUITEM "Nouveau\tCtrl+N", IDM_NEW
       MENUITEM "Paramètres...\tCtrl+P", IDM_PARAM
       MENUITEM SEPARATOR
       MENUITEM "Quitter\tAlt+F4", IDM_QUIT
    END
  POPUP "Aide"
    BEGIN
       MENUITEM "A propos...", IDM_ABOUT
    END
END

LesAccel ACCELERATORS
BEGIN
    "N", IDM_NEW, CONTROL, VIRTKEY
    "P", IDM_PARAM, CONTROL, VIRTKEY
    VK_F1, IDM_ABOUT, VIRTKEY
END

DIALOG2 DIALOG
    60, 60, 182, 70
          STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
                                                         CAPTION "Paramètres"
BEGIN
    DEFPUSHBUTTON "OK", IDOK, 36, 42, 42, 12
    PUSHBUTTON "Cancel", IDCANCEL, 96, 42, 42, 12
    EDITTEXT IDE_EDIT1, 88, 15, 74, 12
    LTEXT "Titre de la fenêtre", -1, 24, 18, 60, 10
END

DIALOG1 DIALOG
   60, 60, 160, 80
          STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
                                                   CAPTION "A propos"
BEGIN
    DEFPUSHBUTTON "Ok", IDOK, 56, 50, 42, 12
    ICON 2, -1, 20, 15, 32, 32
    LTEXT "Mon beau programme !", -1, 60, 18, 80, 10
END

winmain.c :

#include <windows.h>

#include "resource.h"

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog2Proc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG msg;
    WNDCLASS wc;
    HACCEL haccel;

    hinst = hinstance;

    wc.style = 0 ;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(2));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName =  "LEMENU";
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
                                                   NULL, NULL, hinstance, NULL);
    if (!hwnd)  return FALSE;

    ShowWindow(hwnd, nCmdShow);

    haccel = LoadAccelerators(hinstance, "LesAccel");

    while (GetMessage(&msg, NULL, 0, 0))
    {
      if (!TranslateAccelerator(hwnd, haccel, &msg))
          {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
          }
    }
    return msg.wParam;
}
/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HWND hEdit;
    static BOOL EditNotChg = TRUE;
    switch (uMsg)
    {
        case WM_CREATE:
            {
             HFONT hFont;
             hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "edit", "Texte",
               WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
                                            0, 0, 0, 0, hwnd, NULL, hinst, NULL);
             hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
             SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
             SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
                                                                MAKELONG(5, 5));
             return 0;
            }

        case WM_CLOSE:
            if(EditNotChg ||
               MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
                            ,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
                                                            DestroyWindow(hwnd);
            return 0;

        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);

            if(LOWORD(wParam) == IDM_NEW)
                if(EditNotChg ||
                  MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer votre travail ?"
                            ,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
                      {
                              SendMessage(hEdit,WM_SETTEXT,0,(LPARAM)"");
                              EditNotChg = TRUE;
                      }

            if(LOWORD(wParam) == IDM_ABOUT)
                       DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc);

            if(LOWORD(wParam) == IDM_PARAM)
                       DialogBoxParam(hinst, "DIALOG2" , hwnd,
                                            (DLGPROC)Dialog2Proc, (LPARAM)hwnd);

            if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;

            return 0;

        case WM_SIZE:
             MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
             return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}
/******************************************************************************/

BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    switch (uMsg)
    {
      case WM_INITDIALOG:

         return TRUE;

      case WM_COMMAND:
         if (LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK)
                {
                   EndDialog(hDlg,0);
                   return TRUE;
                }

      default:
         return FALSE;
    }
}
/******************************************************************************/

BOOL APIENTRY Dialog2Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    static HWND hParent;
    switch (uMsg)
    {
      case WM_INITDIALOG:
          {
            char st[256];
            hParent = (HWND)lParam;
            GetWindowText(hParent, st, 255);
            SetDlgItemText(hDlg, IDE_EDIT1, st);
          }
         return TRUE;

      case WM_COMMAND:
         if (LOWORD(wParam) == IDOK )
                {
                   char st[256];
                   GetDlgItemText(hDlg, IDE_EDIT1, st, 255);
                   SetWindowText(hParent,st);
                   EndDialog(hDlg,0);
                   return TRUE;
                }
         if (LOWORD(wParam) == IDCANCEL )
                {
                   EndDialog(hDlg,0);
                   return TRUE;
                }
      default:
         return FALSE;
    }
}

J'ai testé les compilations avec C++ Builder et DevC++.

A vos PC.

CGi

Avec la contribution de pharaonix pour la relecture.


Les bases de la programmation

  • Forums
  • Tutoriels
  • Magazine
  • FAQs
  • Blogs
  • Projets
  • Chat
  • Newsletter
  • Études
  • Emploi
  • Club
  • Contacts
  • Accueil Actualités IT Pro
  • ALM Cycle de vie du logiciel
    • ALM
    • Merise
    • UML
  • Java Plateforme et langage Java
    • Java
    • Java Web
    • Spring
    • Android
    • Eclipse
    • NetBeans
  • .NET Microsoft Framework .NET
    • Microsoft DotNET
    • Visual Studio
    • ASP.NET
    • C#
    • VB.NET
    • Windows Phone
    • Microsoft Azure
  • Dév. Web Développement Web et Webmarketing
    • Développement Web
    • AJAX
    • Apache
    • ASP
    • CSS
    • Dart
    • Flash / Flex
    • JavaScript
    • PHP
    • Ruby & Rails
    • Web sémantique
    • Webmarketing
    • (X)HTML
  • EDI Environnements de Développement Intégré
    • EDI
    • 4D
    • Delphi
    • Eclipse
    • LabVIEW
    • NetBeans
    • MATLAB
    • Scilab
    • Visual Studio
    • WinDev
    • Visual Basic 6
    • Lazarus
    • Qt Creator
  • Programmation Programmation et langages de programmation applicatifs
    • Programmation
    • Débuter - Algorithmique
    • 2D - 3D - Jeux
    • Assembleur
    • C
    • C++
    • Go
    • Objective C
    • Pascal
    • Perl
    • Python
    • Qt
    • XML
    • Autres
  • SGBD Systèmes de Gestion de Bases de Données
    • SGBD & SQL
    • 4D
    • Access
    • Big Data
    • DB2
    • Firebird
    • InterBase
    • MySQL
    • NoSQL
    • PostgreSQL
    • Oracle
    • Sybase
    • SQL-Server
  • Office Bureautique pour l'entreprise
    • Microsoft Office
    • Access
    • Excel
    • Word
    • Outlook
    • PowerPoint
    • SharePoint
    • Microsoft Project
  • Solutions d'entreprise Autres logiciels pour l'entreprise
    • Solutions d'entreprise
    • Big Data
    • Business Intelligence
    • ERP / PGI
    • CRM
    • SAS
    • Cloud Computing
    • SAP
    • Microsoft BizTalk Server
    • Talend
  • Applications Applications logicielles
    • Applications
    • Libres & Open Source
    • OpenOffice & LibreOffice
    • Projets
  • Mobiles Logiciels et matériels mobiles
    • Mobiles
    • Android
    • iOS
    • Windows Phone
  • Systèmes Logiciels et matériels systèmes
    • Systèmes
    • Windows
    • Linux Professionnel
    • Sécurité
    • PC
    • Mac
    • Réseau
    • Green IT
    • Virtualisation
    • Systèmes Embarqués
  • EDI
  • 4D
  • Delphi
  • Eclipse
  • LabVIEW
  • NetBeans
  • MATLAB
  • Scilab
  • Visual Studio
  • WinDev
  • Visual Basic 6
  • Lazarus
  • Qt Creator
  • Actualités Lazarus
  • Forum Lazarus
  • Tutoriels Lazarus
  • Tutoriels Pascal
  • F.A.Q. Lazarus
  • F.A.Q. Pascal
  • Sources Lazarus
  • Livres Lazarus

Les bases de la programmation

Table des matières Plier Déplier

  • Introduction
    • À propos de l'auteur
      • Note de la rédaction de Developpez.com
  • 1re partie
    • Premières notions de programmation
      • But de ce chapitre
      • Un peu d'histoire
      • L'ordinateur
        • Codage binaire
          • Le bit
          • Le transistor
          • L'octet et autres unités
            • Le codage des nombres entiers en base 2
            • Le codage du texte en ASCII
        • Mémoire vive et adresse
          • Langage machine et programme exécutable
        • Processus
        • Les entrées-sorties
        • Les interruptions
      • Les Interfaces graphiques
        • Historique
        • Interface du système
          • Le curseur
          • Gestion du clic
        • Fenêtre d'une application
        • Les évènements
        • Les zones de texte
      • La programmation
        • Notion de programme
        • Notion de langage
        • Fichiers sources
        • Compilation
          • Notion de compilateur
          • Exemple de compilation
            • Interprétation
        • Les EDI
        • La programmation évènementielle
      • Lazarus
        • Notion de formulaire
        • Génération automatique de code
        • Programmation évènementielle
          • Ordre d'exécution des instructions
        • Notice d'utilisation
      • Les variables
        • La notion de variable
          • Les noms de variables en Pascal
        • La notion de type
        • Déclaration d'une variable
          • Déclaration d'une variable en Pascal
            • Syntaxe générale
            • Déclaration d'un entier
            • Le type double
            • Déclaration d'une chaîne de caractères
            • Regroupement des déclarations
            • Où faut-il déclarer les variables ?
      • Lecture et affichage
        • Données et résultats d'un traitement
          • L'image du sourd-muet qui ne sait pas additionner
          • Pour un ordinateur
          • Exemple du programme d'addition
        • Historique
          • La procédure writeln
          • La procédure readln
        • ETBib
          • Exemple d'utilisation des procédures de ETBib
            • Lecture des variables
            • Affichage des variables
            • Effacement des zones de texte
          • Argumentation pédagogique
      • Les expressions
        • Les littéraux
          • Les littéraux numériques
          • Les littéraux chaînes de caractères
        • Les constantes
          • Déclaration d'une constante en Pascal
        • Opérateur
          • La concaténation
        • Validité d'une expression
          • Expressions valides de type chaîne de caractères
          • Expressions valides de type numérique
        • Affichage d'expression
          • Expression de type chaîne de caractères
          • Expression numérique
          • Traitement sans variables-résultats
      • L'affectation
        • Écriture d'une instruction d'affectation en Pascal
          • Exemple de code Pascal avec des affectations
        • Affectation récursive
        • Traitement itératif
      • Exemple et conseils
        • Exemple de programme et conseil
          • Cahier des charges
          • Déclaration des constantes
          • Conception de l'interface graphique
          • Lecture et déclaration des données
          • Traitement, résultats intermédiaires et finals
          • Le code de la procédure en entier
        • Synthèse
      • Exercices
  • 2e partie
    • Sous-programmes
      • But de ce chapitre
      • Introduction
        • Découpage d'un programme en sous-programmes
        • Sous-programmes non évènementiels
        • Les unités
        • Procédures et fonctions
      • Portée des variables
        • Exemple 1 (fichier : Global/ProjetGlobal.lpi)
        • Exemple 2 (fichier : Local0/ProjetLocal0.lpi)
        • Exemple 3 (fichier : Local1/ProjetLocal1.lpi)
        • Exemple 4 (fichier : Local2/ProjetLocal2.lpi)
        • Synthèse
          • Définition
          • Portée
          • Durée de vie
          • Déclarations locales multiples
          • Déclaration simultanée en local et en global
      • Exemple d'utilisation de sous-programmes
        • Utilisation des procédures
          • Exemple 1
          • Programmation descendante
          • Retour d'un sous-programme
          • Exemple 2
            • Détail technique
        • Exemple d'utilisation de fonctions
          • Ancienne version du projet
          • Nouvelle version : appel des fonctions
          • Nouvelle version : déclaration des fonctions
          • Retour du résultat
      • Principes généraux et compléments
        • Déclaration et appel de procédures
          • Déclaration d'une procédure
            • Entête
            • Variables locales
          • Écriture d'un appel de procédure
          • Exécution d'un appel de procédure
            • Principe du retour à la procédure appelante
            • Passage des paramètres et exécution de l'appel
        • Déclaration et appel de fonctions
          • Déclaration d'une fonction
          • Appel de fonction
            • Différences avec un appel de procédure
          • Les fonctions prédéfinies
          • Expressions et appels de fonctions
      • Exercices
  • 3e partie
    • Structures de contrôle
      • But de ce chapitre
      • Les expressions logiques
        • Variable booléenne
        • Opérateurs logiques
          • La négation
          • La conjonction
          • La disjonction
        • Expressions logiques
          • Les variables booléennes
          • Utilisation des opérateurs logiques
          • Utilisation des opérateurs de comparaison
            • Les six opérateurs
            • Comparaison d'expressions numériques
            • Comparaison de chaînes de caractères
        • Exercices
      • La conditionnelle
        • Le If
          • Le If … Then
          • Le If … Then … Else
          • If imbriqués
          • L'instruction Exit
        • Case
        • Exercices
      • Les boucles
        • Les boucles While
          • Vocabulaire associé aux boucles
          • Déroulement de la boucle
        • Les boucles Repeat
        • Les boucles For
        • Exercices
      • Corrigés des exercices
  • 4e partie
    • Les tableaux
      • But de ce chapitre
      • Les tableaux à une dimension
        • Exemple introductif
        • Le cas général
        • Utilisation de constantes pour définir la dimension d'un tableau
      • Tableaux remplis partiellement
        • Représentation
        • Adjonction d'une valeur
          • Adjonction à la fin
          • Insertion
        • Suppression d'un élément
          • Suppression du dernier élément
          • Suppression d'un élément quelconque (différent du dernier)
        • Notion de pile
      • Les tableaux à deux dimensions
        • Déclaration en Pascal
        • Traitement de tous les éléments
        • Traitement d'une ligne
        • Traitement d'une colonne
      • Exercices
  • 5e partie
    • Les types structurés
      • Introduction
      • Les types structurés
        • Généralités indépendantes du langage de programmation
        • Déclaration
          • Déclaration d'un type structuré
          • Déclaration de variables de type structuré
        • Manipulation des champs d'une structure
        • Définition de types complexes par combinaison
          • Tableaux de structures : les tables
          • Structure avec champs de type structuré
      • Les pointeurs
        • Déclaration d'un pointeur
        • Allocation dynamique de mémoire
      • Listes simplement chaînées
        • Représentation
        • Parcours
        • Adjonction d'un élément
          • En début de liste
          • Insertion après un élément
        • Suppression d'un élément
          • Suppression du premier élément
          • Suppression du suivant
      • Listes doublement chaînées
        • Représentation
        • Adjonction d'un élément
          • En début de liste
          • Insertion après un élément
      • Exercices
  • 6e partie
    • Les fichiers
      • But de ce chapitre
      • Notion de fichier
        • Qu'est-ce qu'un fichier ?
        • Nom de fichier
        • Chemin d'accès
        • Identification d'un fichier par son nom complet
        • Format
        • Les fichiers texte
        • Les fichiers structurés
      • Opérations sur les fichiers
        • Les erreurs d'accès
        • Ouverture et fermeture d'un fichier
          • À quoi sert l'ouverture d'un fichier ?
          • À quoi sert la fermeture d'un fichier ?
        • Accès direct et accès séquentiel
          • Accès direct
          • Accès séquentiel
        • Manipulation des fichiers texte
          • Ouverture d'un fichier texte
          • Opérations sur les lignes
        • Manipulation des fichiers structurés
          • Ouverture d'un fichier structuré
          • Accès séquentiel
          • Accès direct
      • Manipulation de fichiers texte en Pascal
        • Déclaration
        • Assignation
        • Création d'un fichier texte
          • Ouverture en écriture
          • Écriture d'une ligne
        • Lecture d'un fichier texte
          • Ouverture d'un fichier texte en lecture
          • Lecture d'une ligne et test de fin de fichier
      • Les fichier structurés en Pascal
        • Déclaration
        • Assignation
        • Ouverture et fermeture
        • Écriture séquentielle
        • Lecture séquentielle
          • Lecture jusqu'en fin de fichier
      • Exercices
Ce cours peut être utile à toute personne désirant s'initier à la programmation et aux bases de données. Il s'adresse donc à des débutants et ne demande aucune connaissance préalable dans ces deux domaines.
Depuis 2008, je le remets continuellement à jour en corrigeant les inévitables erreurs et en le rendant plus complet et surtout, plus pédagogique.
Je l'utilise pour enseigner la partie développement d'applications du programme de BTS informatique (formation Bac+2). Il s'agit donc d'un cours « vécu sur le terrain », qui tient compte des difficultés rencontrées par les étudiants.
8 commentaires Donner une note à l'article (5)
Lire l'article.

Article lu 15851 fois.

L'auteur

Eric Thirion Site personnel

L'article

Publié le 16 décembre 2013 - Mis à jour le 5 mars 2014

Public visé : débutant

Version en ligne

Version PDF Version hors-ligne

ePub , Azw et Mobi

Lire l'article

Liens sociaux

Google Bookmarks ! Share on Google+

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Eric Thirion. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique Lazarus : Alcatîz -

Developpez.com

Nous contacter

Participez

Informations légales

Services

Forum Lazarus

Blogs

Hébergement

Partenaires

PlanetHoster
Ikoula

Copyright © 2000-2014 - www.developpez.com


Les bases de la programmation

  • Forums
  • Tutoriels
  • Magazine
  • FAQs
  • Blogs
  • Projets
  • Chat
  • Newsletter
  • Études
  • Emploi
  • Club
  • Contacts
  • Accueil Actualités IT Pro
  • ALM Cycle de vie du logiciel
    • ALM
    • Merise
    • UML
  • Java Plateforme et langage Java
    • Java
    • Java Web
    • Spring
    • Android
    • Eclipse
    • NetBeans
  • .NET Microsoft Framework .NET
    • Microsoft DotNET
    • Visual Studio
    • ASP.NET
    • C#
    • VB.NET
    • Windows Phone
    • Microsoft Azure
  • Dév. Web Développement Web et Webmarketing
    • Développement Web
    • AJAX
    • Apache
    • ASP
    • CSS
    • Dart
    • Flash / Flex
    • JavaScript
    • PHP
    • Ruby & Rails
    • Web sémantique
    • Webmarketing
    • (X)HTML
  • EDI Environnements de Développement Intégré
    • EDI
    • 4D
    • Delphi
    • Eclipse
    • LabVIEW
    • NetBeans
    • MATLAB
    • Scilab
    • Visual Studio
    • WinDev
    • Visual Basic 6
    • Lazarus
    • Qt Creator
  • Programmation Programmation et langages de programmation applicatifs
    • Programmation
    • Débuter - Algorithmique
    • 2D - 3D - Jeux
    • Assembleur
    • C
    • C++
    • Go
    • Objective C
    • Pascal
    • Perl
    • Python
    • Qt
    • XML
    • Autres
  • SGBD Systèmes de Gestion de Bases de Données
    • SGBD & SQL
    • 4D
    • Access
    • Big Data
    • DB2
    • Firebird
    • InterBase
    • MySQL
    • NoSQL
    • PostgreSQL
    • Oracle
    • Sybase
    • SQL-Server
  • Office Bureautique pour l'entreprise
    • Microsoft Office
    • Access
    • Excel
    • Word
    • Outlook
    • PowerPoint
    • SharePoint
    • Microsoft Project
  • Solutions d'entreprise Autres logiciels pour l'entreprise
    • Solutions d'entreprise
    • Big Data
    • Business Intelligence
    • ERP / PGI
    • CRM
    • SAS
    • Cloud Computing
    • SAP
    • Microsoft BizTalk Server
    • Talend
  • Applications Applications logicielles
    • Applications
    • Libres & Open Source
    • OpenOffice & LibreOffice
    • Projets
  • Mobiles Logiciels et matériels mobiles
    • Mobiles
    • Android
    • iOS
    • Windows Phone
  • Systèmes Logiciels et matériels systèmes
    • Systèmes
    • Windows
    • Linux Professionnel
    • Sécurité
    • PC
    • Mac
    • Réseau
    • Green IT
    • Virtualisation
    • Systèmes Embarqués
  • EDI
  • 4D
  • Delphi
  • Eclipse
  • LabVIEW
  • NetBeans
  • MATLAB
  • Scilab
  • Visual Studio
  • WinDev
  • Visual Basic 6
  • Lazarus
  • Qt Creator
  • Actualités Lazarus
  • Forum Lazarus
  • Tutoriels Lazarus
  • Tutoriels Pascal
  • F.A.Q. Lazarus
  • F.A.Q. Pascal
  • Sources Lazarus
  • Livres Lazarus

Les bases de la programmation

Table des matières Plier Déplier

  • À propos de l'auteur
    • Note de la rédaction de Developpez.com

Image non disponible

Eric Thirion, titulaire d'un doctorat en informatique et d'un Capes de mathématiques, a passé plusieurs années à l'étranger (de 1990 à 1994 à l'Université Heriot-Watt d'Édimbourg, puis de 1994 à 1996 à l'université de Leuven en Belgique) dans le cadre de contrats de recherche en vision par ordinateur.

Auteur d'une vingtaine de publications, il a enseigné la programmation à l'école des Mines de Nancy, à l'IUT de Nancy et à l'université Louis Pasteur de Strasbourg.

Actuellement, il est professeur d'informatique en BTS-SIO à l'école Estudia de Strasbourg.

Note de la rédaction de Developpez.com▲

La relecture des différentes parties du cours a été réalisée par Cédric DUPREZ, Claude Leloup, f-leb et Philippe DUVAL.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Eric Thirion. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique Lazarus : Alcatîz -

Developpez.com

Nous contacter

Participez

Informations légales

Services

Forum Lazarus

Blogs

Hébergement

Partenaires

PlanetHoster
Ikoula

Copyright © 2000-2014 - www.developpez.com


Les bases de la programmation

  • Forums
  • Tutoriels
  • Magazine
  • FAQs
  • Blogs
  • Projets
  • Chat
  • Newsletter
  • Études
  • Emploi
  • Club
  • Contacts
  • Accueil Actualités IT Pro
  • ALM Cycle de vie du logiciel
    • ALM
    • Merise
    • UML
  • Java Plateforme et langage Java
    • Java
    • Java Web
    • Spring
    • Android
    • Eclipse
    • NetBeans
  • .NET Microsoft Framework .NET
    • Microsoft DotNET
    • Visual Studio
    • ASP.NET
    • C#
    • VB.NET
    • Windows Phone
    • Microsoft Azure
  • Dév. Web Développement Web et Webmarketing
    • Développement Web
    • AJAX
    • Apache
    • ASP
    • CSS
    • Dart
    • Flash / Flex
    • JavaScript
    • PHP
    • Ruby & Rails
    • Web sémantique
    • Webmarketing
    • (X)HTML
  • EDI Environnements de Développement Intégré
    • EDI
    • 4D
    • Delphi
    • Eclipse
    • LabVIEW
    • NetBeans
    • MATLAB
    • Scilab
    • Visual Studio
    • WinDev
    • Visual Basic 6
    • Lazarus
    • Qt Creator
  • Programmation Programmation et langages de programmation applicatifs
    • Programmation
    • Débuter - Algorithmique
    • 2D - 3D - Jeux
    • Assembleur
    • C
    • C++
    • Go
    • Objective C
    • Pascal
    • Perl
    • Python
    • Qt
    • XML
    • Autres
  • SGBD Systèmes de Gestion de Bases de Données
    • SGBD & SQL
    • 4D
    • Access
    • Big Data
    • DB2
    • Firebird
    • InterBase
    • MySQL
    • NoSQL
    • PostgreSQL
    • Oracle
    • Sybase
    • SQL-Server
  • Office Bureautique pour l'entreprise
    • Microsoft Office
    • Access
    • Excel
    • Word
    • Outlook
    • PowerPoint
    • SharePoint
    • Microsoft Project
  • Solutions d'entreprise Autres logiciels pour l'entreprise
    • Solutions d'entreprise
    • Big Data
    • Business Intelligence
    • ERP / PGI
    • CRM
    • SAS
    • Cloud Computing
    • SAP
    • Microsoft BizTalk Server
    • Talend
  • Applications Applications logicielles
    • Applications
    • Libres & Open Source
    • OpenOffice & LibreOffice
    • Projets
  • Mobiles Logiciels et matériels mobiles
    • Mobiles
    • Android
    • iOS
    • Windows Phone
  • Systèmes Logiciels et matériels systèmes
    • Systèmes
    • Windows
    • Linux Professionnel
    • Sécurité
    • PC
    • Mac
    • Réseau
    • Green IT
    • Virtualisation
    • Systèmes Embarqués
  • EDI
  • 4D
  • Delphi
  • Eclipse
  • LabVIEW
  • NetBeans
  • MATLAB
  • Scilab
  • Visual Studio
  • WinDev
  • Visual Basic 6
  • Lazarus
  • Qt Creator
  • Actualités Lazarus
  • Forum Lazarus
  • Tutoriels Lazarus
  • Tutoriels Pascal
  • F.A.Q. Lazarus
  • F.A.Q. Pascal
  • Sources Lazarus
  • Livres Lazarus

Les bases de la programmation

Table des matières Plier Déplier

  • Premières notions de programmation
    • But de ce chapitre
    • Un peu d'histoire
    • L'ordinateur
      • Codage binaire
        • Le bit
        • Le transistor
        • L'octet et autres unités
          • Le codage des nombres entiers en base 2
          • Le codage du texte en ASCII
      • Mémoire vive et adresse
        • Langage machine et programme exécutable
      • Processus
      • Les entrées-sorties
      • Les interruptions
    • Les Interfaces graphiques
      • Historique
      • Interface du système
        • Le curseur
        • Gestion du clic
      • Fenêtre d'une application
      • Les évènements
      • Les zones de texte
    • La programmation
      • Notion de programme
      • Notion de langage
      • Fichiers sources
      • Compilation
        • Notion de compilateur
        • Exemple de compilation
          • Interprétation
      • Les EDI
      • La programmation évènementielle
    • Lazarus
      • Notion de formulaire
      • Génération automatique de code
      • Programmation évènementielle
        • Ordre d'exécution des instructions
      • Notice d'utilisation
    • Les variables
      • La notion de variable
        • Les noms de variables en Pascal
      • La notion de type
      • Déclaration d'une variable
        • Déclaration d'une variable en Pascal
          • Syntaxe générale
          • Déclaration d'un entier
          • Le type double
          • Déclaration d'une chaîne de caractères
          • Regroupement des déclarations
          • Où faut-il déclarer les variables ?
    • Lecture et affichage
      • Données et résultats d'un traitement
        • L'image du sourd-muet qui ne sait pas additionner
        • Pour un ordinateur
        • Exemple du programme d'addition
      • Historique
        • La procédure writeln
        • La procédure readln
      • ETBib
        • Exemple d'utilisation des procédures de ETBib
          • Lecture des variables
          • Affichage des variables
          • Effacement des zones de texte
        • Argumentation pédagogique
    • Les expressions
      • Les littéraux
        • Les littéraux numériques
        • Les littéraux chaînes de caractères
      • Les constantes
        • Déclaration d'une constante en Pascal
      • Opérateur
        • La concaténation
      • Validité d'une expression
        • Expressions valides de type chaîne de caractères
        • Expressions valides de type numérique
      • Affichage d'expression
        • Expression de type chaîne de caractères
        • Expression numérique
        • Traitement sans variables-résultats
    • L'affectation
      • Écriture d'une instruction d'affectation en Pascal
        • Exemple de code Pascal avec des affectations
      • Affectation récursive
      • Traitement itératif
    • Exemple et conseils
      • Exemple de programme et conseil
        • Cahier des charges
        • Déclaration des constantes
        • Conception de l'interface graphique
        • Lecture et déclaration des données
        • Traitement, résultats intermédiaires et finals
        • Le code de la procédure en entier
      • Synthèse
    • Exercices

Premières notions de programmation▲

But de ce chapitre▲

Dans ce cours, j'ai tout d'abord rassemblé les notions qui m'ont semblé utiles à introduire avant de commencer proprement dit le cours de programmation.

En particulier, comme la programmation est conditionnée par la manière dont fonctionne un ordinateur, il m'a semblé logique de commencer par une présentation très sommaire de cette machine : sa structure, son fonctionnement, et du seul langage qu'elle est capable de comprendre : le langage machine.

J'aborde ensuite les principes d'interaction homme-machine via une souris, un clavier et un écran graphique. Cela me permet d'introduire la programmation évènementielle, avant de parler de l'environnement de développement intégré Lazarus, spécialement conçu pour ce type de programmation.

Le cours de programmation proprement dit porte sur les instructions permettant de rentrer des données dans la mémoire d'un ordinateur (la lecture, l'affectation) ou d'afficher des informations à l'écran, ce qui demande au préalable d'introduire les notions de variables, de type et d'expressions.

Un peu d'histoire▲

De la machine de Turing à UML, retrouvez les moments-clés de l'histoire de l'informatique dans cette série de slides : téléchargez le document Projo-Histoire.pdfTéléchargez le document Projo-Histoire.pdf.

L'ordinateur▲

Un ordinateur est une machine utilisant et produisant de l'information. Il reçoit de l'information (de l'utilisateur, par internet…), donne de l'information (informations affichées à l'écran, imprimées…), mémorise de l'information (en mémoire vive, sur disque dur, clé USB, CD, DVD…) et traite de l'information (calcul mathématique, « raisonnement » logique…).

Nous allons donc commencer par parler de l'information et plus exactement de la manière dont elle est représentée dans un ordinateur.

Codage binaire▲

Le bit▲

Toute information traitée par un ordinateur est codée en binaire, c'est-à-dire par une suite de bits valant zéro ou un.

La représentation physique du zéro et du un dépend du type de support utilisé : mémoire vive, disque dur, clé USB, CD, etc. Mais, quel que soit le type de support utilisé, il s'agit toujours d'un point de vue logique, d'une suite de zéros et de uns.

Le transistor▲

Pour prendre un exemple, la représentation physique actuelle d'un bit en mémoire vive est un transistor composé de silicium. La représentation du zéro et du un est basée sur la répartition des charges négative et positive dans le transistor.

Voici par exemple le zéro :

Image non disponible

Et voici le un :

Image non disponible

De 1970 à 2008, le nombre de transistors sur une puce électronique a augmenté de manière vertigineuse en suivant la loi de Moore : le nombre de transistors par puce double tous les deux ans pour le même prix de fabrication. Les transistors deviennent donc de plus en plus petits.

Image non disponible

Comme un signal électrique met moins de temps à parcourir une distance plus courte, cette progression a des répercussions directes sur la vitesse des processeurs (elle double tous les 18 mois). Elle entraîne également une augmentation constante de la capacité des mémoires vives (la RAM des PC double tous les deux ans environ).

En 2008, la taille minimale d'un transistor était de l'ordre de 45 milliardièmes de mètre.

L'octet et autres unités▲

Une suite de huit bits est appelée un octet (ou byte en anglais). C'est l'unité de base permettant de définir la dimension d'un espace de stockage. Il en dérive des unités plus grandes comme :

  • le kilooctet (ko = 210 octets = 1 024 octets) ;
  • le mégaoctet (Mo = 1 024 Ko, soit environ un million d'octets) ;
  • le gigaoctet (1 024 Mo, soit environ un milliard d'octets) ;
  • le téraoctet (1 024 Go, soit environ mille milliards d'octets).

La mémoire vive des ordinateurs actuels (2013) est de l'ordre de plusieurs gigaoctets alors que la capacité des disques durs se compte en téraoctets.

La manière de coder une information en binaire dépend du type d'information. Les informations numériques, par exemple, ne sont pas codées de la même manière que le texte ou que les images ou le son.

Pour fixer les idées, voyons deux exemples de codage.

Le codage des nombres entiers en base 2▲

Le codage binaire des nombres entiers est basé sur l'écriture des nombres en base 2 (en utilisant uniquement les chiffres 0 et 1).

Le principe est le même que celui de l'écriture d'un nombre entier en base 10. c'est-à-dire l'écriture habituelle d'un nombre utilisant les 10 chiffres de 0 à 9. Rappelons que dans ce système, chaque chiffre est associé à une puissance de 10. Il y a un chiffre pour les unités (100), un chiffre pour les dizaines (101), un pour les centaines (102), un pour les milliers (103), etc. Par exemple, 2013 signifie : 2 × 103 + 0 × 102 + 1 × 101 + 3 × 100.

L'écriture d'un nombre entier en base 2 utilise le même principe, sauf que les puissances de 10 sont remplacées par des puissances de 2. Le nombre 40, s'écrira par exemple : 101000. Car 40 = 32 + 8 ou autrement dit 25 + 23 et donc 40 = 1 × 25 + 0 × 24 + 1 × 23 + 0 × 22 + 0 × 21 + 0 × 20.

Comme 40 n'a que six chiffres en base 2, on peut représenter ce nombre sur un octet comme suit :

Image non disponible

Ce codage permet de coder de grands nombres en utilisant très peu de bits :

  • jusqu'à 64 000 environ avec 2 octets pour les entiers non signés (32 000 avec signe) ;
  • jusqu'à 4 milliards environ avec 4 octets pour les entiers non signés (2 milliards avec signe).
Le codage du texte en ASCII▲

Les informations textuelles (suite de caractères) sont souvent codées en ASCII. Dans ce codage, chaque caractère est représenté par un octet de valeur prédéfinie dans une table de correspondance.

La « valeur » est en fait l'interprétation du codage binaire de l'octet en nombre entier, comme nous l'avons vu au paragraphe précédent.

Prenons par exemple, le caractère « ( ». Dans la table de correspondance ASCII, on voit que sa valeur est 40. Autrement dit, le codage binaire du caractère « ( » est :

Image non disponible

c'est-à-dire le même codage que celui du nombre 40 sur un octet !

Mais alors comment un ordinateur peut-il distinguer un nombre d'un caractère ?

Il ne le peut pas !

En effet, la simple connaissance d'une suite de 0 et de 1 ne permet pas de savoir ce que représente cette suite. Tout dépend de la manière dont elle est interprétée.

Si on donne l'octet précédent à un ordinateur, il y verra le nombre 40, si à ce moment-là il s'attend à un nombre et il y verra le caractère « ( », si à ce moment-là, il s'attend à un caractère.

Mémoire vive et adresse▲

Image non disponible

Barrette de RAM DDR3 de 4 Go

La mémoire vive (ou RAM pour Random Access Memory) d'un ordinateur peut être vue comme une suite continue d'octets.

Chaque octet possède un numéro. Le numéro du premier est 0, du deuxième 1, etc. Ce numéro s'appelle l'adresse de l'octet.

Une plage mémoire est une suite d'octets consécutifs. Elle peut donc être définie par une plage d'adresses (adresses minimale et maximale) ou bien par l'adresse de début (ou autrement dit, l'adresse minimale) de la zone et du nombre d'octets qu'elle contient.

Voici, par exemple, une plage mémoire de trois octets, démarrant à l'adresse 2 :

Image non disponible

Langage machine et programme exécutable▲

Image non disponible

Processeur Intel 80486DX2 (taille: 12 × 6,75 mm)

Le processeur d'un ordinateur exécute en permanence des instructions que l'on appelle instructions machine. Il contient (en outre) des mémoires de petite taille (quelques octets), appelées registres, qui servent à transférer des données de la mémoire vers le processeur (ou inversement) en vue d'effectuer des opérations sur ces données.

Une instruction machine est un ordre donné au processeur.

Par exemple :

  • copier une plage mémoire d'adresse donnée dans un registre ;
  • copier le contenu d'un registre dans une plage mémoire d'adresse donnée ;
  • additionner, soustraire, diviser ou multiplier les nombres contenus dans deux registres et enregistrer le résultat dans un troisième registre.

Nous avons vu plus haut que toute information traitée par un ordinateur est codée en binaire. Il en de même des instructions machine. Une instruction machine est codée en binaire sur quelques octets (4 ou 8 dans les machines actuelles). Une partie du codage définit le type d'opération à effectuer et une autre, les objets (les registres, par exemple) sur lesquels cette opération doit agir.

La manière de coder les instructions machine dépend du type de processeur.

On appelle langage machine l'ensemble de conventions définissant le codage binaire des instructions machine d'un type particulier de processeur.

Une suite d'instructions machine constitue un programme ou plus précisément un programme exécutable.

Il est important de noter que le langage machine est le seul langage directement exécutable par un ordinateur. Les langages de programmation (comme C, Java, PHP…) ne le sont pas !

Les programmes exécutables peuvent se trouver en mémoire (on dit qu'ils sont résidents) ou sur le disque, sous la forme de fichiers (reconnaissables sous Windows par leur extension .exe). Mais pour pouvoir être exécutés, ils doivent forcément être chargés en mémoire :

Image non disponible

Nous avons dit plus haut qu'un programme exécutable est une suite d'instructions.

En fait, pour être plus exact, il faudrait définir un programme exécutable comme un mélange de zones de code contenant des instructions machine et de zones de données servant à stocker provisoirement les informations traitées (données ou résultats).

Image non disponible

Processus▲

La mémoire d'un ordinateur contient plusieurs programmes que le processeur exécute à tour de rôle à une telle vitesse que l'on a l'impression qu'ils sont exécutés en même temps.

Chacun de ces programmes résidents possède donc ses propres zones d'instructions et de données.

Image non disponible

L'exécution d'un programme particulier par un ordinateur s'appelle un processus.

Notez qu'un même programme peut donner lieu à plusieurs processus.

Parmi les processus résidents figurent nécessairement les processus système, qui servent (entre autres) à gérer les périphériques (clavier, souris, écran, disque, lecteur de CD, imprimantes, etc.) et les fichiers.

Les entrées-sorties▲

Jusqu'à présent, nous avons essentiellement décrit l'ordinateur du point de vue du processeur et de la mémoire vive. Le processeur exécute en permanence des programmes chargés en mémoire. Ces programmes sont des suites d'instructions machine et de plages mémoire réservées aux données.

Mais si les instructions exécutées par le processeur ne font que transférer des informations entre la mémoire vive et les registres, comment peut-il communiquer avec le clavier ? Avec la souris ? Avec l'écran ?

Comment les plages mémoire réservées aux données peuvent-elles recevoir les données de l'utilisateur ?

Comment peuvent-elles s'afficher à l'écran ?

Image non disponible

Pour répondre à ces questions, il nous faut tout d'abord compléter notre vision d'un ordinateur et considérer les éléments externes à l'unité centrale que l'on appelle les périphériques (écran, clavier, souris, disque, lecteur de CD, clé USB, imprimante, etc.).

Les périphériques ont leurs propres circuits spécialisés appelés interfaces d'entrées-sorties. Pour l'écran, par exemple, il s'agit de la carte graphique. Ces circuits peuvent avoir leurs propres registres, leur propre mémoire et même leurs propres processeurs.

Ils communiquent avec le processeur et la mémoire par un ensemble de fils que l'on appelle le bus de données.

En réalité, les instructions exécutées par le processeur ne se limitent pas à la mémoire centrale. Certaines instructions spéciales, appelées instructions d'entrée-sortie, permettent de transférer des données entre les interfaces d'entrée-sortie et les registres du processeur ou directement avec la mémoire vive.

Image non disponible

Ceci explique très sommairement comment se fait la communication entre le processeur et les périphériques et indirectement, comment un programme peut envoyer des informations vers un périphérique ou recevoir des informations provenant d'un périphérique.

Mais cela n'explique pas l'interaction avec l'utilisateur. En effet, vous remarquerez que votre ordinateur réagit instantanément (ou quasiment) lorsque vous éjectez un CD, introduisez une clé USB, bougez la souris, appuyez sur une touche du clavier, etc.

Ces actions physiques de l'utilisateur sur l'ordinateur peuvent se produire à n'importe quel moment, alors que le processeur est occupé à exécuter les instructions machine d'un programme quelconque. Or c'est nécessairement le processeur qui doit réagir à ces actions.

Il faut donc qu'il soit capable d'interrompre l'exécution du programme en cours pour réagir immédiatement à n'importe quelle action de l'utilisateur.

Comment cela est-il possible ?

Les interruptions▲

La réponse à cette question se trouve dans le mécanisme d'interruption.

Une interruption (physique) est un signal envoyé au processeur par un dispositif externe à l'unité centrale.

Le clavier va par exemple envoyer un signal d'interruption au processeur chaque fois qu'une touche est appuyée. La souris envoie une interruption chaque fois qu'elle est déplacée ou que l'utilisateur effectue un clic.

Le processeur possède des entrées spécialement prévues pour recevoir ces signaux.

Lorsqu'il reçoit une interruption, il interrompt provisoirement le programme qu'il est en train d'exécuter pour exécuter des instructions du système d'exploitation prévues, pour traiter ce type d'interruption. Il existe par exemple des instructions prévues pour traiter les interruptions clavier, d'autres pour traiter des interruptions souris, etc.

Image non disponible

Le système ne gère pas entièrement les interruptions ; elles sont sous-traitées par les pilotes (drivers en anglais) des périphériques. Un pilote est un programme spécialisé dans la gestion d'un périphérique spécifique. Il ne fait pas partie du système d'exploitation, car il est généralement produit par le fabricant du périphérique.

Une fois l'interruption traitée, le processeur reprendra ce qu'il était en train de faire à l'endroit exact où il s'était arrêté.

Pour donner une image, on pourrait comparer ce mécanisme au réflexe humain. Prenons par exemple une personne lisant un livre. Le cerveau de cette personne joue le rôle du processeur. Le programme exécuté est la lecture du livre. Soudain, une abeille pique la personne. La douleur perçue est le signal d'interruption envoyé au cerveau (le processeur). Par réflexe, la personne lâche le livre. C'est une réaction préprogrammée dans l'inconscient (le système d'exploitation). Après avoir soigné la piqûre (traitement de l'interruption), la personne reprend la lecture du livre à l'endroit où elle s'était arrêtée.

Les Interfaces graphiques▲

Historique▲

Les premières interfaces graphiques sont apparues avec l'invention de la souris (1967) et des écrans graphiques, c'est-à-dire capables d'afficher des images.

Avant cela, les écrans d'ordinateur n'affichaient que du texte et ressemblaient plutôt à ceci :

Image non disponible

En 1973 apparaît le premier ordinateur avec interface graphique : la station graphique Xerox Alto fabriquée en Californie, à Palo Alto, par la société Xerox (par ailleurs à l'origine de la machine à photocopier).

Image non disponible

La station Xerox Alto

En 1983, cet ordinateur a inspiré le premier PC de Apple, le LISA, ancêtre des Macintosh puis en 1985, le premier système Windows (Windows 1.0) de Microsoft.

Image non disponible

Le Lisa

C'est ainsi que sont apparus les premiers systèmes d'exploitation munis d'interfaces graphiques, dans lesquelles les applications apparaissent sous forme de fenêtres ou d'icônes sur lesquelles on peut agir grâce à la souris.

Interface du système▲

De manière générale, l'interface graphique d'un programme est la manière dont il se présente visuellement à l'écran lorsqu'il s'exécute.

Dans cette partie, nous décrivons plus particulièrement l'interface graphique du système qui apparaît au démarrage d'un ordinateur. Dans Windows, par exemple, elle est constituée du bureau et d'une barre de tâches.

Image non disponible

Le bureau contient des icônes de différents programmes. La barre des tâches donne accès (entre autres) au menu Démarrer, par l'intermédiaire duquel on peut accéder à différents composants du système, configurer l'ordinateur, arrêter l'ordinateur, etc.

Tout ceci constitue l'interface graphique du système d'exploitation.

Le curseur▲

On voit également apparaître le curseur, qui se présente généralement sous la forme d'une petite flèche. Le curseur permet d'indiquer sur quel élément de l'interface graphique on veut agir.

Lorsque l'utilisateur déplace la souris, il provoque une interruption gérée par le système d'exploitation. Le traitement par défaut de cette interruption consiste à déplacer le curseur.

Gestion du clic▲

Comme nous l'avons vu, un clic provoque une interruption, qui est tout d'abord interceptée par le système d'exploitation. Le traitement de cette interruption dépendra de la position du curseur. Nous reviendrons là-dessus plus en détail un peu plus loin.

Lorsque l'on clique sur l'icône d'un programme, par exemple, le système le charge en mémoire et lance son exécution. L'interface graphique de ce programme s'affiche alors à l'écran.

Fenêtre d'une application▲

L'interface graphique d'un programme se présente généralement sous la forme d'un rectangle, appelé fenêtre, dans lequel apparaissent des composants ou contrôles. Les composants sont par exemple des menus, des barres d'outils, des zones de texte…

Voici par exemple une partie de l'interface graphique d'Excel 2003 :

Image non disponible

Les zones de texte sont des zones rectangulaires de l'écran dans lesquelles il est possible de taper du texte sur une ligne. Nous reviendrons plus en détail sur ce composant graphique.

À un moment donné, l'écran contient une (ou plusieurs) fenêtre(s) pour chaque application ouverte. Par exemple, une fenêtre pour Internet Explorer, une fenêtre pour Word, une autre pour Photoshop, etc. Mais parmi toutes ces fenêtres, il y a une seule fenêtre active : c'est la fenêtre qui apparaît au premier plan et qui contient le curseur.

Image non disponible

Les évènements▲

Lorsqu'il se produit une interruption souris (déplacement de la souris ou clic) ou clavier (touche appuyée ou relâchée), il y a deux possibilités :

  1. Il n'y a aucune fenêtre active. Dans ce cas, l'interruption est directement gérée par le système. Sous Windows, c'est par exemple le cas lorsque vous cliquez sur le bureau ;
  2. Il y a une fenêtre active. Dans ce cas, le système intercepte tout d'abord l'interruption, mais il ne la gère pas forcément entièrement. Une partie de la gestion de l'interruption est sous-traitée à l'application associée à cette fenêtre. Pour cela, il communique toutes les informations utiles à l'application sous la forme d'un évènement. Les parties d'une l'application qui interceptent les évènements et les gèrent sont appelés des gestionnaires d'évènements.

Image non disponible

Un évènement est lié à un composant particulier de la fenêtre, que l'on pourrait appeler le composant actif : c'est le composant contenant le curseur au moment de l'évènement.

Une application pourra ainsi savoir sur quel composant l'utilisateur a cliqué ou quel était le composant actif lorsqu'il a appuyé sur telle touche du clavier.

Les zones de texte▲

Les interfaces graphiques sont généralement conçues pour éviter au maximum de taper du texte. Mais il arrive que ce soit inévitable. Par exemple, pour donner le nom d'un fichier, une adresse web, une recherche par mots-clés, etc.

Dans une interface graphique, la saisie de texte se fait par l'intermédiaire des zones de texte, zones rectangulaires dans lesquelles il est possible de « rentrer » du texte.

Pour cela, l'utilisateur doit tout d'abord activer la zone de texte en cliquant dedans. Le curseur change alors de forme. En général, il se transforme en une barre verticale clignotante, ce qui signifie que la zone de texte est active et prête à recevoir du texte.

Lorsqu'une zone de texte est active, les caractères frappés au clavier viennent s'afficher à l'écran à l'intérieur de celle-ci. On a ainsi l'impression que les caractères sont directement transférés du clavier vers l'écran. En réalité, ils sont tout d'abord transférés en mémoire par le système d'exploitation.

En effet, à chaque zone de texte est associée une plage mémoire, appelée buffer, destinée à mémoriser les caractères frappés.

Supposons qu'une zone de texte soit active et que l'utilisateur appuie sur la touche « s ». Le système va alors transférer le caractère « s » dans le buffer associé à cette zone de texte, puis dans un deuxième temps, afficher le contenu du buffer à l'écran à l'intérieur de la zone de texte.

Image non disponible

S'il appuie ensuite sur la touche « t », le caractère « t » va être rajouté dans le buffer après le « s », puis le buffer va être réaffiché.

Ce mécanisme est très important, car c'est essentiellement de cette manière que des données sont communiquées à un programme. Nous reviendrons là-dessus lorsque nous parlerons de la lecture des données.

La programmation▲

Les langages de programmation ont été introduits pour éviter l'écriture directe de programmes en langage machine (tâche extrêmement fastidieuse, pour ne pas dire impossible !).

Mais tout d'abord, qu'est-ce qu'un programme ?

Notion de programme▲

Un programme écrit dans un langage de programmation se présente sous la forme d'un texte dans lequel figurent les instructions à exécuter. Dans ce texte, les plages mémoire contenant des données portent un nom. Ce sont les variables du programme.

Voici par exemple une instruction écrite en Pascal :

Sélectionnez
  somme := x + y;

Cette instruction signifie : additionner les nombres contenus dans les variables x et y et stocker le résultat dans la variable somme.

Dans les cours de programmation, un programme est souvent défini comme une suite d'instructions. Cette définition n'est pas tout à fait exacte, car les programmes contiennent également des déclarations. Une déclaration permet (entre autres) de définir la nature des variables sur lesquelles le programme doit travailler.

Voici par exemple une déclaration de variable écrite en Pascal :

Sélectionnez
var x, y , somme : integer;

Ici le programmeur a déclaré que les variables x, y et somme doivent contenir des nombres entiers.

Notion de langage▲

Un langage de programmation est un ensemble de conventions définissant la manière d'écrire un programme sous la forme d'un texte.

Ces conventions comprennent des conventions syntaxiques (comparables à la grammaire d'une langue naturelle) et lexicales (comparables à l'orthographe d'une langue naturelle).

Elles sont évidemment différentes d'un langage à l'autre. Voici par exemple l'instruction d'addition vue plus haut traduite dans différents langages de programmation :

Image non disponible

On constate que dans certains langages (comme PHP et LISP), les déclarations de variables ne sont pas obligatoires. Ces langages sont dits non typés.

Tous les langages de programmation possèdent des mots spéciaux, que l'on appelle des mots-clés (dans l'exemple ci-dessus, ces mots figurent en gras). Les mots-clés ont une signification particulière prédéfinie dans le langage de programmation. Ils ne peuvent donc pas être utilisés par le programmeur pour signifier autre chose.

En Pascal, par exemple, le mot-clé Integer signifie nombre entier.

Fichiers sources▲

Un programme écrit dans un langage de programmation se présente sous la forme d'un ou plusieurs fichiers appelés fichiers sources (par opposition aux fichiers exécutables).

Ces fichiers contiennent les programmes tels qu'ils ont été initialement écrits par les développeurs. Ce sont des textes écrits en un langage de programmation particulier, que l'on peut visualiser à l'aide d'un éditeur de texte (WordPad, par exemple).

Mais, comme nous l'avons déjà dit plus haut, le seul langage de programmation directement exécutable par un ordinateur est le langage machine adapté à son processeur.

Les fichiers sources doivent donc être traduits en langage machine avant de pouvoir être exécutés. On appelle cela la compilation. Mais nous verrons plus loin que la compilation n'est pas la seule approche possible : il en existe une autre appelée interprétation.

Compilation▲

Notion de compilateur▲

Certains langages, le C par exemple, ne peuvent être que compilés. Les langages de ce type sont appelés langages compilés. Pour pouvoir les utiliser, vous devez avoir installé sur votre ordinateur un compilateur spécifique à ce langage.

Image non disponible

Le compilateur est un programme exécutable qui permet de traduire un programme écrit dans un langage de programmation en langage machine.

Par exemple, pour pouvoir programmer en C, vous devez nécessairement avoir installé un compilateur C sur votre machine.

Après avoir été compilé, le programme peut être exécuté en chargeant sa version exécutable en mémoire :

Image non disponible

Exemple de compilation▲

Pour fixer un peu les idées, reprenons le programme d'addition en Pascal et voyons comment il pourrait être traduit en langage machine.

Les variables déclarées sont tout d'abord associées à des plages mémoires :

Image non disponible

Les instructions sont ensuite traduites en instructions machine portant sur les registres et les plages mémoire associées aux variables :

Image non disponible

Dans cet exemple, nous n'avons pas donné le code binaire des instructions machine, car il serait sans intérêt. On voit qu'une simple instruction écrite en langage de programmation peut correspondre à de nombreuses instructions machine.

Interprétation▲

Les programmes écrits en langages interprétés (Java, Visual Basic, PHP…) peuvent être exécutés sans avoir été auparavant entièrement traduits en langage machine.

Ils font appel à un interpréteur.

Tout comme un compilateur, un interpréteur est spécifique à un langage donné.

Il existe, par exemple, un interpréteur Java, un interpréteur Visual Basic, un interpréteur PHP…

Contrairement aux programmes compilés, les programmes interprétés ne sont pas directement exécutés par l'ordinateur, mais par l'interpréteur (qui, lui, est exécuté par la machine !).

Pour exécuter un programme interprété, il faut tout d'abord lancer l'interpréteur, puis charger le code source du programme en mémoire. Ce code source peut ensuite être exécuté par l'interpréteur :

Image non disponible

L'interpréteur s'exécute donc toujours « en même temps » que le programme interprété.

Notez que certains interpréteurs (comme Visual Basic) peuvent également fonctionner en tant que compilateur. Cela permet d'obtenir une version plus rapide et commercialisable du programme, lorsque la mise au point est terminée. En effet :

  • l'exécution d'un programme compilé est beaucoup plus rapide que son interprétation ;
  • sous sa forme exécutable, un programme peut être vendu sans divulguer les fichiers sources, ainsi qu'à des utilisateurs ne possédant pas l'interpréteur sur leur machine.

Les EDI▲

Le développement d'application (ou si vous préférez la programmation) se fait aujourd'hui grâce à des logiciels appelés EDI (pour Environnement de Développement Intégré) ou IDE en anglais. Un EDI peut être adapté à un langage de programmation particulier (Delphi, Lazarus pour le langage Pascal, Visual C++ pour le langage C++) ou à plusieurs langages (Eclipse pour Java, PHP, JavaScript).

Un EDI comprend en général un éditeur de texte spécialement adapté au langage, un compilateur et/ou un interpréteur, un débogueur (outil de mise au point) et, surtout, un outil de développement d'interface graphique.

Avant l'existence des EDI, la conception de l'interface graphique d'un programme était extrêmement fastidieuse, car le développeur devait lui-même préciser dans le programme les positions et dimensions de tous les composants de l'interface.

Avec un EDI cela se fait simplement en « dessinant » en quelque sorte l'interface graphique à l'aide de la souris. Évidemment, les instructions permettant d'afficher l'interface existent toujours, mais au lieu d'être données par le programmeur, elles sont générées automatiquement par l'EDI.

La programmation évènementielle▲

Avec l'apparition des interfaces graphiques dans les années 80, la programmation est devenue plus complexe. Les programmes devaient d'une part produire tout le design de l'interface et d'autre part pouvoir réagir aux évènements, c'est-à-dire aux actions de l'utilisateur sur les différents composants de cette interface.

Il est alors apparu une nouvelle manière de programmer, appelée programmation évènementielle, qui consiste à associer des gestionnaires d'évènements aux composants de l'interface.

Image non disponible

Un des premiers langages permettant la programmation évènementielle a été le langage Visual Basic de Alan Cooper (première version en 1991) et son environnement de développement intégré (un des premiers également). Le concept a ensuite été repris par Borland avec Delphi.

Image non disponible

Alan Cooper - inventeur du Visual Basic

Un des points les plus intéressants des EDI est qu'ils facilitent la programmation évènementielle. Rappelons qu'avec un EDI, l'interface graphique peut être « dessinée » par le programmeur. Il peut ensuite associer un gestionnaire d'évènements (initialement vide) à un composant de l'interface en cliquant sur celui-ci.

Lazarus▲

Image non disponible

Lazarus est un environnement de développement intégré associé au langage Pascal Objet. C'est la version open source du logiciel Delphi de Borland. Ce logiciel facilite en particulier la création d'interfaces graphiques et la programmation évènementielle par un mécanisme de génération automatique de code.

Notion de formulaire▲

Sous Lazarus, une fenêtre s'appelle un formulaire.

Voici, par exemple, un formulaire réalisé avec Lazarus :

Image non disponible

Ce formulaire comporte trois étiquettes, trois zones de texte et un bouton.

Image non disponible

Nous avons vu que les zones de texte permettent à l'utilisateur de saisir des données. En fait, elles servent également à afficher des résultats.

Image non disponible

Dans notre exemple, les deux zones de texte étiquetées X et Y servent à saisir les deux nombres à additionner. Ce sont les données du programme. La zone de texte étiquetée X+Y sert à afficher la somme de ces deux nombres. C'est le résultat du programme produit par l'ordinateur.

Les étiquettes servent donc en principe à indiquer à l'utilisateur quelle donnée il doit introduire dans la zone de texte située juste à côté ou bien quel résultat y sera affiché.

Lorsque l'utilisateur clique sur le bouton Additionner, après avoir saisi les deux nombres dans les zones de texte prévues à cet effet, le programme affiche le résultat dans la zone de texte étiquetée X+Y.

Un bouton sert donc à déclencher un traitement d'information particulier (dans notre cas, l'addition de deux nombres).

Génération automatique de code▲

La création d'une nouvelle application engendre automatiquement la création d'un formulaire (initialement vide) associé à un fichier source Pascal appelé unité (Unit en anglais).

Image non disponible

Le formulaire de départ

Par défaut, ce formulaire s'appelle Form1 et l'unité Pascal associée, Unit1. Si le développeur ne la renomme pas autrement, elle sera représentée sur disque par le fichier Unit1.pas.

Dès la création du formulaire, l'unité qui lui est associée contient du code Pascal généré automatiquement.

Le voici :

Image non disponible

Au fur et à mesure que le programmeur ajoute des composants sur le formulaire, leurs noms apparaissent automatiquement dans le code source. Avec l'exemple précédent du programme d'addition, ce serait Label1, Label2, Label3 pour les étiquettes ; Edit1, Edit2, Edit3 pour les zones de texte ; Button1 pour le bouton.

Image non disponible

Voici le code source de l'unité associée au programme d'addition, après que le programmeur a déposé les différents composants sur le formulaire :

Image non disponible

Ces noms permettront d'accéder aux composants par programme. Il ne faut pas les confondre avec leurs libellés. Les libellés apparaissent sur l'interface graphique. Les noms quant à eux sont invisibles.

Par exemple, le nom du bouton est Button1, mais son libellé est Additionner.

Pour pouvoir plus facilement identifier les composants dans le programme, le développeur peut leur donner des noms plus significatifs que ceux générés automatiquement par Lazarus. Par exemple, BoutonAdditionner au lieu de Button1 pour le bouton, ZoneTexteX et ZoneTexteY pour les zones de texte contenant les deux nombres à additionner, ZoneTexteSomme pour la zone de texte contenant la somme des deux nombres.

La modification des noms des composants se fait via l'interface graphique de Lazarus. Ils sont alors automatiquement modifiés dans le code.

Voici le code modifié automatiquement après modification des noms :

Image non disponible

Programmation évènementielle▲

L'interface graphique de Lazarus est conçue pour faciliter la programmation évènementielle.

Rappelons que la programmation évènementielle consiste à associer des gestionnaires d'évènements à des évènements de l'interface graphique. Un évènement est défini par un composant de l'interface et un type d'interruption. Par exemple, un bouton (composant) et un clic sur ce bouton (interruption). Lorsque cet évènement a effectivement lieu, les instructions contenues dans le gestionnaire d'évènements sont automatiquement exécutées.

Sous Lazarus, un gestionnaire d'évènements s'appelle une procédure évènementielle.

Pour associer une procédure évènementielle à un composant, le développeur n'a besoin de taper aucune ligne de code. Il lui suffit en gros de cliquer sur le composant et éventuellement de sélectionner le type d'interruption. Un double-clic sur un composant sélectionne l'interruption par défaut. La déclaration de la procédure évènementielle est alors automatiquement générée dans le code avec un nom formé à partir du nom du composant et du nom de l'interruption.

Reprenons l'exemple de notre programme d'addition.

Supposons que le développeur souhaite associer une procédure évènementielle au bouton Additionner, qui serait activée lorsque l'utilisateur clique sur ce bouton.

Pour cela, il lui suffit de faire un double-clic sur ce bouton et la déclaration de cette procédure évènementielle sera automatiquement générée dans le code source, avec aucune instruction à l'intérieur.

Image non disponible

Le nom de cette procédure est BoutonAdditionnerClick. Il est formé à partir du nom du composant (ici BoutonAdditionner) et du nom de l'interruption (Click = clic de souris).

Le développeur complètera ensuite ce gestionnaire d'évènements en plaçant des instructions à l'intérieur (entre begin et end), qui seront automatiquement exécutées lorsque l'utilisateur cliquera sur ce bouton.

Ordre d'exécution des instructions▲

En général, un programme ne s'exécute pas dans l'ordre des lignes du code source, c'est-à-dire de la première à la dernière ligne du fichier.

Considérons un programme dont l'interface graphique possède trois boutons A, B et C :

Image non disponible

Dans le code source, on a d'abord la procédure évènementielle du bouton B, puis celle du bouton A, puis celle du bouton C. Mais l'ordre d'exécution de ces procédures est totalement indépendant de l'ordre dans lequel elles figurent dans le fichier source. Cela dépend évidemment de l'ordre dans lequel l'utilisateur cliquera sur les boutons A, B et C. Une même procédure peut même être exécutée plusieurs fois.

Lorsqu'une procédure évènementielle a fini de s'exécuter, le programme retourne dans un état d'attente d'évènement.

Notice d'utilisation▲

Téléchargez ici la notice d'utilisation de Lazarus.

Les variables▲

La notion de variable▲

La notion de variable existe dans tous les langages de programmation.

Les variables servent à conserver temporairement des données en mémoire.

Une variable peut être vue comme une plage mémoire dans une zone de données d'un programme. Elle porte un nom et possède à un instant donné une valeur.

On peut donc parler de l'adresse d'une variable : c'est l'adresse du début la plage mémoire qui lui est réservée.

Voici par exemple quatre variables nommées Age, Nom, NombreEnfant et Prenom.

Image non disponible

La valeur d'une variable varie lors de l'exécution d'un programme (d'où le nom de variable justement !). La variable NombreEnfant pourra par exemple valoir successivement 2, 0, 3, 1, 0, 4…

Par contre, le nom d'une variable ne change pas : il est le même du début à la fin de l'exécution d'un programme.

Les noms de variables en Pascal▲

En Pascal, les noms de variables doivent respecter les règles suivantes :

Règle1 : le premier caractère est obligatoirement une lettre ;

Règle2 : les seuls caractères autorisés sont les lettres (sans accents), les chiffres et « _ » ;

Règle3 : un nom de variable ne peut pas être un mot-clé du langage.

Voici quelques exemples et contre-exemples de nom de variables :

Nom | Validité | Règles non respectées
Prenom | Oui |
Prénom | Non | 2
Nom101 | Oui |
Nom_Eleve | Oui |
Nom Eleve | Non | 2
_Age | Non | 1
Age-Eleve | Non | 2
Integer | Non | 3

Attention : la casse n'a pas d'importance, c'est-à-dire que le Pascal ne distingue pas les minuscules des majuscules. Nom_Eleve, nom_Eleve, nom_eleve désignent donc la même variable.

La notion de type▲

Il existe différents types de variables.

Le type d'une variable définit la manière dont elle est représentée en mémoire et les opérations que l'on peut faire dessus.

Il existe, par exemple, des variables de type chaîne de caractères, qui vont servir à stocker du texte en mémoire. La variable Prenom pourrait, par exemple, être de ce type. À un instant donné, sa valeur pourrait être 'Astérix', à un autre moment 'Obélix' ou encore à un autre moment 'Abraracourcix'.

Il existe aussi des variables de type entier. Ce pourrait, par exemple, être les variables Age, ou NombreEnfant.

La représentation en mémoire d'une variable dépend de son de type. En particulier, deux variables de types distincts n'occupent pas en général la même place mémoire :

Image non disponible

D'autre part, les opérations que l'on peut faire sur deux variables de types différents ne sont pas les mêmes. Par exemple, on ne pourra pas multiplier deux chaînes de caractères, alors que l'on peut très bien multiplier deux entiers.

Déclaration d'une variable▲

La déclaration d'une variable sert à définir son type et, par conséquent, la manière dont elle sera représentée en mémoire. Elle a pour effet de réserver une plage mémoire pour stocker la valeur de cette variable. Le type de la variable définit la taille de cette plage et la manière de coder ses valeurs.

Toute variable doit (en principe) être déclarée, avant de pouvoir être utilisée. En Pascal, la déclaration est obligatoire. D'autres langages (comme Visual Basic) autorisent une déclaration implicite avec un type par défaut. Malheureusement, cela autorise également des erreurs de programmation…

La manière de déclarer une variable dépend évidemment du langage de programmation, mais on y trouve toujours les mêmes ingrédients : le nom de la variable et son type.

Déclaration d'une variable en Pascal▲
Syntaxe générale▲

Une déclaration de variable en Pascal utilise le mot-clé Var suivi du nom de la variable, du caractère « : », puis du nom du type et d'un point-virgule.

Sélectionnez
Var Nom de la variable : nom du type ;
Déclaration d'un entier▲

Prenons par exemple la variable Age.

En Pascal, le type entier s'appelle Integer. La déclaration de la variable Age se ferait donc comme ceci :

Sélectionnez
Var Age : Integer ;
Le type double▲

Les nombres entiers ne sont pas les seuls nombres avec lesquels un ordinateur peut calculer. Il y a également les nombres décimaux (comme 0.5 ou 3.14 par exemple). Ces nombres peuvent par exemple (il y a d'autres possibilités) être représentés par le type Double en Pascal.

Déclaration d'une chaîne de caractères▲

Prenons par exemple la variable Prenom, de type chaîne de caractères.

En Pascal, le type chaîne de caractères s'appelle String. La déclaration de la variable Prenom s'écrira donc :

Sélectionnez
Var Prenom : String ;
Regroupement des déclarations▲

Remarquez qu'il est inutile de répéter le mot-clé var. De plus, les variables de même type peuvent être regroupées : on sépare les noms des variables par des virgules puis on précise leur type par : nom du type ;

Exemple :

Sélectionnez
Var Nom, Prenom : String ; Age, NombreEnfant : Integer;
Où faut-il déclarer les variables ?▲

Pour qu'une variable soit utilisable à n'importe quel endroit d'une unité Pascal, elle doit être déclarée entre var et implementation, comme dans l'exemple suivant :

Sélectionnez
var
  Form1: TForm1;
  // Déclaration des variables globales
  x, y , somme : integer;

implementation

Nous avons déclaré ici trois variables de type entier : x, y et somme.

Les variables déclarées de cette manière sont appelées variables globales. Cela signifie qu'elles peuvent être utilisées dans tout le code du fichier où elles sont déclarées.

Lecture et affichage▲

Données et résultats d'un traitement▲

De nombreux traitements informatiques peuvent se schématiser suivant les trois étapes suivantes :

Image non disponible

L'image du sourd-muet qui ne sait pas additionner▲

Pour bien comprendre le sens des termes lecture, traitement et affichage, mettez-vous à la place de l'ordinateur. Votre rôle est d'additionner mentalement deux nombres pour un sourd-muet qui ne sait pas calculer (l'utilisateur).

Comme il ne peut pas parler, il vous écrit les deux nombres à additionner sur un bout de papier, qui joue le rôle de l'écran.

Vous ne pouvez pas additionner les deux nombres si vous ne les connaissez pas. Il faut donc commencer par les lire. C'est la première étape de lecture des données.

À présent, les deux nombres à additionner sont dans votre mémoire. Vous pouvez donc effectuer le traitement, qui consiste à additionner mentalement ces deux nombres. C'est l'étape de traitement des données.

Vous avez le résultat. Mais si vous ne le communiquez pas à l'utilisateur, c'est comme si vous n'aviez rien fait. Comme il est sourd, vous allez écrire le résultat sur le bout de papier. C'est la troisième étape : l'affichage des résultats du traitement.

Pour un ordinateur▲

Pour un ordinateur c'est un peu la même chose :

  1. Lire les données signifie transférer des informations affichées à l'écran vers la mémoire, ou plus précisément vers une variable-donnée, c'est-à-dire une variable prévue pour stocker une donnée du traitement.
    Image non disponible
  2. Traiter les données signifie grosso modo faire des calculs qui dépendent des données pour produire des résultats. En principe, mais ce n'est pas obligatoire, chaque résultat est stocké dans une variable-résultat. La programmation de cette étape sera vue plus loin dans le cours lorsque nous parlerons d'expression et d'affectation.
    Image non disponible
  3. Si les résultats ont été sauvegardés dans des variables-résultats, Afficher les résultats signifie afficher la valeur de chacune de ces variables à l'écran. Il est également possible d'afficher les résultats d'un traitement sans les avoir au préalable sauvegardés dans des variables. Mais il serait difficile pour l'instant d'expliquer comment. Nous verrons cela un peu plus loin.
    Image non disponible
Exemple du programme d'addition▲

Reprenons l'exemple de l'addition de deux nombres.

Lire les données signifierait dans ce cas lire les valeurs des deux nombres à additionner depuis (par exemple) deux zones de texte pour les stocker dans deux variables données X et Y.

Image non disponible

Traiter les données signifierait additionner les valeurs des variables X et Y, puis mémoriser le résultat dans une variable Somme.

Image non disponible

Enfin, afficher les résultats signifierait afficher la valeur de la variable somme dans la zone de texte prévue à cet effet.

Image non disponible

Historique▲

Avant l'apparition des interfaces graphiques, tous les langages de programmation avaient des procédures (cette notion sera abordée dans le chapitre sous-programmes) pour lire et afficher.

En Pascal, par exemple, elles s'appellent readln (pour la lecture) et writeln (pour l'écriture).

La procédure writeln▲

La procédure writeln vous permettait d'afficher un texte à l'écran. Par exemple l'instruction :

Sélectionnez
writeln ('Hello world !');

vous affichait le texte « Hello Word » à l'écran, comme ceci (en supposant que l'écran était vide avant l'exécution de l'instruction) :

Image non disponible

Avec la procédure writeln, vous pouviez également afficher la valeur d'une variable à l'écran. Par exemple, en supposant que la variable Somme contienne la valeur 2013, l'instruction :

Sélectionnez
writeln (Somme);

vous affichait « 2013 » à l'écran, comme ceci :

Image non disponible

Chaque writeln affichait donc une nouvelle ligne à la suite des lignes déjà affichées à l'écran.

La procédure readln▲

La procédure readln permettait de lire la valeur d'une variable tapée au clavier (et donc affichée à l'écran).

Supposons par exemple qu'un programme exécute l'instruction suivante :

Sélectionnez
readln (X);

où X représente un nombre entier.

Dès que l'instruction est exécutée, le programme est momentanément interrompu. Le système d'exploitation prend alors la main et range chaque caractère saisi par l'utilisateur dans un buffer associé au clavier. Dès que l'utilisateur appuie sur la touche Entrée, le contenu du buffer est renvoyé au programme qui stocke alors la valeur correspondante dans la variable X et continue son exécution.

ETBib▲

ETBib est une unité Pascal que j'ai écrite afin d'introduire des procédures équivalentes à readln et writeln fonctionnant avec une interface graphique.

Ces procédures sont les suivantes :

  • Lire, LireEntier et LireNombre : pour lire une variable depuis une zone de texte ;
  • Afficher, AfficherEntier, et AfficherNombre : pour afficher une variable dans une zone de texte ou une zone de liste ;
  • Effacer : pour effacer le contenu d'une zone texte ou d'une zone de liste.

Ces procédures ont été introduites pour des raisons pédagogiques (explications pour le lecteur averti). Donc gardez bien à l'esprit qu'elles ne font pas partie du langage Pascal.

Exemple d'utilisation des procédures de ETBib▲

Pour expliquer le fonctionnement des procédures de ETBib, nous allons prendre un exemple de programme qui permet de lire et d'afficher trois variables :

  • Ville, de type String, est destinée à contenir le nom d'une ville. Sa valeur sera lue et affichée dans la zone de texte ZT_Ville ;
  • CP, de type integer, pour le code postal de la ville, lue et affichée dans la zone de texte ZT_CodePostal ;
  • NH, de type double, pour le nombre d'habitants de la ville exprimé en milliers. Remarquez que ce nombre n'est pas forcément entier : 264,2 représente par exemple 264 200 habitants. La zone de texte correspondante se nomme ZT_Hab.

Voici l'interface graphique du programme :

Image non disponible

Lecture des variables▲

La lecture des trois variables est déclenchée par le bouton Lire. Voici la procédure évènementielle associée :

Sélectionnez
procedure TForm1.BT_LireClick(Sender: TObject);
begin
  Lire (Ville,ZT_Ville);
  LireEntier (CP, ZT_CodePostal);
  LireNombre (NH,ZT_Hab);
end;

La procédure de lecture à utiliser dépend du type de la variable : Lire pour lire une variable de type String, LireEntier pour lire une variable de type Integer et LireNombre pour lire une variable de type Double.

Entre parenthèses figurent :

  • en première position : le nom de la variable à lire ;
  • en deuxième position : le nom de la zone de texte.

Rappelez-vous que chaque zone de texte est associée à un buffer, qui se remplit avec les caractères frappés au clavier.

Lorsque la variable est de type chaîne de caractères, la procédure de lecture ne fait que recopier ces caractères dans la variable.

S'il s'agit d'une variable de type Integer ou Double, il ne suffit pas de recopier le buffer dans la variable. Le contenu du buffer est tout d'abord codé en base 2, puis c'est le résultat de ce codage qui est stocké dans la variable.

Affichage des variables▲

L'affichage des trois variables se fait par le bouton Afficher, dont voici la procédure évènementielle :

Sélectionnez
procedure TForm1.BT_AfficherClick(Sender: TObject);
begin
  Afficher (Ville, ZT_Ville);
  AfficherEntier (CP, ZT_CodePostal);
  AfficherNombre (NH, ZT_Hab);
end;

Comme pour la lecture, il faut choisir la procédure d'affichage en fonction du type de la variable : Afficher pour afficher une variable de type String, AfficherEntier pour une variable de type integer et AfficherNombre pour une variable de type Double.

Dans les parenthèses, on précise le nom de la variable à afficher, puis le nom de la zone de texte dans laquelle on souhaite l'afficher.

La procédure Afficher recopie simplement le contenu de la variable dans le buffer, qui est ensuite affiché dans la zone de texte par le système d'exploitation.

Dans le cas des procédures AfficherNombre et AfficherEntier, un décodage préalable est nécessaire avant de recopier la chaîne de caractères représentant le nombre dans le buffer.

Effacement des zones de texte▲

L'effacement des zones de texte s'effectue avec le bouton Effacer, dont voici la procédure évènementielle :

Sélectionnez
procedure TForm1.BT_EffacerClick(Sender: TObject);
begin
  Effacer (ZT_Ville); Effacer (ZT_Hab);
  Effacer (ZT_CodePostal);
end;

Il suffit ici de préciser entre parenthèses le nom de la zone de texte à effacer.

Argumentation pédagogique▲

Cette section s'adresse au lecteur averti (professeur ou élève avancé) désirant comprendre la raison pour laquelle j'ai introduit les procédures de lecture et d'affichage.

Reprenons notre exemple. Voici comment lire les trois variables sans utiliser les procédures Lire, LireEntier et LireNombre :

Sélectionnez
procedure TForm1.BT_LireClick(Sender: TObject);
begin
  Ville := ZT_Ville.Text;
  CP := StrToInt(ZT_CodePostal.Text);
  NH := StrToFloat (ZT_Hab.Text);
end;

Ce qui me gêne ici est l'utilisation obligatoire de la programmation objet. On accède à l'attribut Text de la classe TEdit. C'est encore pire lorsqu'il s'agit d'afficher du texte dans une zone de liste, puisqu'il faut alors appliquer la méthode Add à l'attribut items (objet de la classe TStringList) d'un objet de la classe TListBox.

Or, je pense que l'on peut difficilement commencer un cours de programmation avec des notions de programmation objet. À mon sens, la programmation objet ne peut être abordée qu'après avoir bien assimilé les types structurés (puisque les objets sont en quelque sorte des types structurés étendus) et les sous-programmes (puisque les méthodes sont des sous-programmes associés à des classes).

Mis à part la programmation objet, d'autres arguments peuvent être ajoutés en faveur de l'utilisation des procédures de lecture et d'affichage :

  • cela rend le code plus lisible ;
  • cela oblige les élèves à programmer plus proprement : les données doivent obligatoirement être transférées dans des variables avant d'être utilisées. On ne travaille pas directement sur les zones de texte pour faire des calculs ;
  • on voit mieux la différence entre une lecture (ou un affichage) et une simple affectation.

Les expressions▲

Vous connaissez déjà tous les expressions mathématiques dans lesquelles on retrouve des nombres, des opérateurs de calculs (+, -, etc.) et des variables. Par exemple :

Sélectionnez
(a + b) × (a - 17)

Dans les langages de programmation, on retrouve également cette notion d'expression sous une forme un peu différente et plus générale.

Une expression est une écriture possédant une valeur et un type, dans laquelle figurent (entre autres) des littéraux, des constantes, des opérateurs et des noms de variables.

Vous savez déjà ce que sont les variables. Par contre, nous n'avons pas encore défini les littéraux, les constantes et les opérateurs.

La valeur d'une expression est le résultat du calcul de cette expression, en tenant compte des valeurs des variables qu'elle contient. Son type est le type du résultat.

L'écriture Age+1, est, par exemple, une expression de type entier, car :

  • la variable Age est de type entier ;
  • 1 est un entier ;
  • la somme de deux entiers est un entier.

Sa valeur est celle de la variable Age augmentée de 1.

Une expression peut faire intervenir plusieurs opérations. Par exemple :

Sélectionnez
  (Age + 1) * 2

représente le double de la valeur de la variable Age augmentée de 1.

Voilà différentes valeurs des expressions Age+1 et (Age+1)*2 pour différentes valeurs de la variable Age :

Age | Age+1 | (Age + 1) * 2
10 | 11 | 22
0 | 1 | 2
43 | 44 | 88

Les littéraux▲

Un littéral est l'écriture d'une valeur d'un certain type de variable.

L'écriture des littéraux obéit à un certain nombre de conventions qui dépendent du type et du langage de programmation.

Les littéraux numériques▲

Un littéral numérique représente la valeur d'un nombre.

Il existe de nombreuses manières d'écrire un nombre. On peut par exemple avoir :

  • une simple suite de chiffres. Cette écriture ne convient que pour les nombres entiers positifs. Exemple : 2012 ;
  • pour pouvoir représenter des entiers relatifs, il faut autoriser la présence d'un signe + ou - devant cette suite de chiffres. Exemple : -15, +320 ;
  • pour pouvoir représenter des nombres non entiers, on utilise le point. La partie entière du nombre est suivie d'un point puis d'une autre suite de chiffres. Exemples : 3.141559, -5.33333 ;
  • pour pouvoir représenter des nombres très grands ou très petits, on autorise également la notation scientifique. Exemple : 1.7E+308.
Les littéraux chaînes de caractères▲

Un littéral de type chaîne de caractères s'écrit entre quotes.

Par exemple : 'Il fait beau', '4012', '(3 x + 1)', 'VisualC++', '@%^šÂ€' sont des littéraux de type chaîne de caractères.

On remarquera que tous les caractères sont autorisés entre les quotes sauf les quotes eux-mêmes ! Pour mettre une quote dans une chaîne de caractères, il faut la faire précéder par une autre quote. Par exemple :

Sélectionnez
    L''horaire d''été'

Parmi les chaînes de caractères, il y en a une qui joue un rôle particulier : la chaîne vide.

La chaîne vide est une chaîne qui ne contient aucun caractère. On l'écrit '' (deux quotes).

Les constantes▲

Une constante est un littéral portant un nom. Avant la compilation (ou l'interprétation) du code, ce nom sera remplacé par le littéral dans tout le code.

Une constante est donc en quelque sorte le synonyme d'une valeur. On pourra par exemple introduire une constante PI représentant le nombre 3.14159. Avant la compilation du code (cette étape s'appelle en fait la précompilation), toutes les occurrences du mot PI seront remplacées par 3.14159.

Comme son nom l'indique, la valeur d'une constante ne peut pas varier durant l'exécution d'un programme.

Déclaration d'une constante en Pascal▲

En Pascal, une constante peut se déclarer de la manière suivante :

Sélectionnez
    Const nom de constante = littéral ;

Le nom d'une constante obéit aux mêmes règles que le nom d'une variable.

Exemple :

Sélectionnez
    Const PI = 3.14159 ;
     CLEF_USB = 'F:' ;  DISQUE_DUR = 'C:';

Opérateur▲

En informatique, un opérateur est généralement représenté par un ou deux caractères représentant une certaine opération.

Voici, par exemple, quelques opérateurs fréquemment rencontrés dans les langages de programmation : + - / * ^ = ++ ! %

Dans ce cours, nous nous concentrerons pour l'instant sur les opérateurs suivants :

  • l'opérateur d'addition, représentée par le caractère + ;
  • l'opérateur de soustraction,représenté par le caractère - ;
  • l'opérateur de multiplication, représenté par le caractère * (et non pas × comme en mathématique !) ;
  • L'opérateur de division, représenté par le caractère / ;
  • l'opérateur de concaténation, représenté par le caractère +.

Vous connaissez déjà les quatre premiers opérateurs, puisqu'il s'agit des opérateurs mathématiques classiques. Mais si vous n'avez jamais fait de programmation, vous ignorez probablement le sens de l'opérateur de concaténation.

La concaténation▲

Concaténer deux chaînes de caractères signifie les mettre bout à bout.

Par exemple, si je concatène la chaîne 'para' avec la chaîne 'pluie', j'obtiens la chaîne 'parapluie'.

La manière de noter l'opérateur de concaténation dépend des langages.

En Pascal, il se note comme l'opérateur d'addition, c'est-à-dire avec un signe +.

L'opérateur de concaténation permet de construire des expressions de type chaîne de caractères.

Par exemple, 'Je suis '+Prenom est une expression de type chaîne de caractères constituée d'un littéral ('Je suis '), de l'opérateur de concaténation + et d'un nom de variable (Prenom).

On peut concaténer autant de chaînes de caractères qu'on le souhaite. Par exemple, dans l'expression 'Je suis '+Prenom+' '+Nom il y a quatre chaînes de caractères.

Voilà différentes valeurs de l'expression

Sélectionnez
 'Je suis ' + Prenom

pour différentes valeurs de la variable Prenom :

Prenom | 'Je suis '+Prenom
'Jimi' | 'Je suis Jimi'
'Albert' | 'Je suis Albert'

Voilà différentes valeurs de l'expression

Sélectionnez
 'Je suis ' + Prenom + ' ' + Nom

pour différentes valeurs des variables Prenom et Nom :

Prenom | Nom | 'Je suis '+Prenom+' '+Nom
'Jimi' | 'Hendrix' | 'Je suis Jimi Hendrix'
'Albert' | 'Einstein' | 'Je suis Albert Einstein'
'Albert' | '' | 'Je suis Albert'

Validité d'une expression▲

L'écriture d'une expression dans un certain langage de programmation doit nécessairement obéir aux règles propres à ce langage.

Une expression de type T est valide si elle peut être « comprise » par l'interpréteur (ou le compilateur) comme un calcul d'une valeur de type T ou plus généralement comme la construction d'un objet de type T.

Nous donnons ici, un certain nombre de règles non exhaustives permettant d'écrire des expressions valides en Pascal.

Expressions valides de type chaîne de caractères▲

Les règles suivantes permettent de construire des expressions de type chaîne de caractères valides :

Règle1 : un nom de variable est une expression valide si et seulement cette variable est de type chaîne de caractères ;

Règle2 : un littéral (ou une constante représentant ce littéral) est une expression valide si et seulement si il est du type chaîne de caractères ;

Règle3 : A+B est valide si et seulement si A et B sont deux expressions valides de type chaîne de caractères ;

Règle4 : un littéral, une constante ou un nom de variable ne pas être suivi immédiatement d'un littéral, d'une constante ou d'un nom de variable.

Exemples d'application de ces règles (Nom et Prenom sont des chaînes de caractères et Age est un entier) :

Expression | Validité | Règle non respectée
Prenom | Oui |
Age | Non | 1
'Thirion' | Oui |
48 | Non | 2
'Eric'+'Thirion' | Oui |
Prenom+Nom | Oui |
Prenom+'Thirion' | Oui |
'Cours de'+Prenom+'Thirion' | Oui |
'Eric'+'Thirion a' +Age+' ans' | Non | 3
'Eric'+'Thirion a' + 48 +' ans' | Non | 3
Prenom Nom | Non | 4
'Eric' 'Thirion' | Non | 4
Prenom 'Thirion' | Non | 4
Expressions valides de type numérique▲

Les règles suivantes (non exhaustives) permettent de construire des expressions numériques valides :

Règle1 : une variable est une expression numérique valide si et seulement si elle est de type numérique (integer ou double par exemple) ;

Règle2 : un littéral (ou une constante représentant ce littéral) est une expression numérique valide si et seulement si il est de type numérique ;

Règle3 : les écritures A+B, A-B, A*B, A/B sont des expressions numériques valides si et seulement si A et B sont des expressions numériques valides ;

Règle4 : un littéral, une constante ou un nom de variable ne peut pas être suivi immédiatement d'un littéral, d'une constante ou d'un nom de variable ;

Règle5 : (A) est une expression numérique valide si et seulement si A est une expression numérique valide ;

Règle6 : chaque parenthèse ouvrante correspond à une et une seule parenthèse fermante.

Exemples d'application de ces règles (avec Nom de type String, Age de type Integer et Poids de type Double) :

Expression | Validité | Règle non respectée
Nom | Non | 1
Age | Oui |
201 | Oui |
20.14 | Oui |
'20.14' | Non | 2
Age * Poids | Oui |
Age + Nom | Non | 3
Poids/(Age*2.2) | Oui |
Age Poids | Non | 4
Age 49 | Non | 4
(Age * Poids) | Oui |
(Age * Nom) | Non | 5
(Poids/((Age*(2.2))) | Oui |
Poids/((Age + 1 ) | Non | 6

Affichage d'expression▲

Les procédures d'affichage de l'unité ETBib permettent d'afficher la valeur d'une expression. Selon le type de l'expression, on utilisera Afficher, AfficherEntier ou AfficherNombre.

Expression de type chaîne de caractères▲

Pour afficher une expression E de type chaîne de caractères dans une zone de texte ou zone de liste Z, on utilisera l'instruction :

Sélectionnez
   Afficher ( E, Z);

Exemple : la variable Nom contient le nom d'une personne et la variable Adresse son adresse. L'instruction suivante permet d'afficher le nom et l'adresse de la personne sur une seule ligne dans la zone de liste ZL_Client :

Sélectionnez
   Afficher (Nom + '-' + Adresse, ZL_Client);

Le nom et l'adresse sont séparés par un tiret.

Expression numérique▲

Pour une expression numérique dont le résultat est toujours un nombre entier, on peut utiliser AfficherEntier ou AfficherNombre. Par contre, si le résultat n'est pas forcément entier, il faudra utiliser AfficherNombre.

Exemple 1 : un hôtel propose des chambres à 47 euros. La variable NJ contient le nombre de jours. L'instruction suivante permet d'afficher le prix du séjour dans la zone de texte ZT_PrixSejour :

Sélectionnez
   AfficherEntier ( NJ * 47 , ZT_PrixSejour);

Dans cet exemple, on peut utiliser la procédure AfficherEntier, car le prix à payer est forcément un nombre entier.

Exemple 2 : supposons à présent que le prix d'une chambre soit variable et pas forcément entier. Ce prix est stocké dans une variable P de type double. Dans ce cas, on ne sait pas si le prix du séjour est un nombre entier. Il faut donc utiliser l'instruction :

Sélectionnez
   AfficherNombre ( NJ * P , ZT_PrixSejour);
Traitement sans variables-résultats▲

Revenons sur le schéma lecture-traitement-affichage. À présent, nous pouvons expliquer comment afficher les résultats d'un traitement sans les sauvegarder dans des variables : il suffit d'écrire l'expression du traitement à l'intérieur de l'instruction d'affichage !

Voilà par exemple comment écrire le programme d'addition sans sauvegarder le résultat dans une variable :

Sélectionnez
 var
  Form1: TForm1;
  // Déclaration des variables globales
  x, y  : integer;

implementation

{ TForm1 }

procedure TForm1.BoutonAdditionnerClick(Sender: TObject);
begin
  // Lecture des données
  LireEntier (x,zoneTexteX);
  LireEntier (y,ZoneTexteY);
  // Traitement et Affichage du résultat
  AfficherEntier (x + y, ZoneTexteSomme);

end;

Le calcul de l'addition se fait à l'intérieur de l'instruction d'affichage.

Dans des cas simples, cette manière de programmer peut être utilisée. Mais de manière générale, essayez autant que possible de séparer le traitement des données et l'affichage. Vous obtiendrez ainsi des programmes plus lisibles et plus faciles à modifier.

L'affectation▲

L'affectation est une instruction qui permet de modifier la valeur d'une variable. Plus précisément :

Affecter une valeur à une variable signifie enregistrer cette valeur dans la plage mémoire allouée à cette variable.

Affecter une valeur à une variable modifie donc l'état de la zone mémoire qui lui est allouée, ou autrement dit son « contenu ».

Reprenons notre exemple avec les quatre variables Age, NombreEnfant, Nom et Prenom. Supposons qu'à un instant donné l'état de la mémoire soit le suivant :

Image non disponible

Si on affecte ensuite la valeur 33 à la variable Age, on obtient :

Image non disponible

Et la zone mémoire allouée à la variable Age restera inchangée jusqu'à ce qu'une valeur différente lui soit affectée.

Écriture d'une instruction d'affectation en Pascal▲

En Pascal, on représente l'affectation par l'opérateur :=. Le nom de la variable figure à gauche de := et la valeur qu'on veut lui affecter, à droite.

Par exemple, affecter la valeur 33 à la variable Age s'écrira :

Sélectionnez
   Age := 33 ;

De manière générale, ce qui figure à droite de := est une expression. Une affectation s'écrit donc sous la forme :

Sélectionnez
    Nom de variable := expression ;

Une affectation est toujours exécutée de droite à gauche. c'est-à-dire que l'expression figurant à droite du signe := est d'abord évaluée et que sa valeur est ensuite enregistrée dans la variable dont le nom figure à gauche du :=.

Exemple de code Pascal avec des affectations▲

Voilà un premier exemple de code Pascal, principalement composé d'affectations :

Sélectionnez
 Var Age, MonAge  : Integer;
 begin
 Age := 20 ;
 MonAge := 47 ;
 Age := Age+1 ;
 Age := Age*2 ;
 MonAge := Age ;
 end;

Ce code commence par la déclaration de deux variables de type entier (Age et MonAge). Après la partie déclaration, on a cinq instructions d'affectation.

Voilà comment se déroule l'exécution de ce code :

Après l'exécution de l'instruction | Age vaut | MonAge vaut
Age := 20 ; | 20 | ?
MonAge := 47 ; | 20 | 47
Age := Age + 1 ; | 21 | 47
Age := 2 * Age ; | 42 | 47
MonAge := Age ; | 42 | 42

Remarque : la première valeur de la variable MonAge est indéterminée. Tant que l'on n'a pas affecté de valeur à une variable, on ne sait pas ce qu'elle contient.

Affectation récursive▲

Dans une affectation, l'expression affectée à la variable peut elle-même contenir la variable. On dit alors que l'affectation est récursive.

Exemple 1

Ajouter le prix d'un article au prix total, se traduit par l'affectation récursive suivante :

Sélectionnez
 PrixTotal := PrixTotal + Prix ;

Exemple 2

Augmenter une variable de 1, se dit incrémenter. C'est un cas particulier d'affectation récursive très fréquemment utilisé en programmation.

Par exemple, pour incrémenter le nombre d'articles :

Sélectionnez
 NombreArticle := NombreArticle + 1 ;

Traitement itératif▲

Les affectations récursives sont en général utilisées dans des traitements itératifs, c'est-à-dire des traitements que l'on répète plusieurs fois. Chaque répétition d'un traitement itératif s'appelle une itération.

Illustrons ceci par un exemple.

Considérons un programme permettant de calculer la durée totale des morceaux d'un album. Voici son interface graphique :

Image non disponible

Le bouton Ajouter ajoute le nouveau titre saisi dans la zone de liste et augmente la durée totale de l'album par la durée de ce morceau.

Nous avons ici un exemple de traitement itératif, puisque les mêmes opérations vont être effectuées chaque fois que l'utilisateur ajoute un album. Ou autrement dit, chaque fois qu'il clique sur le bouton Ajouter.

Voici, le code de la procédure évènementielle de ce bouton :

Sélectionnez
procedure TForm1.BT_AjouterClick(Sender: TObject);
begin
  //--- Lecture des données
  Lire (Titre, ZT_Titre);
  LireEntier (Duree, ZT_Duree);

  //--- Traitement des données
  DureeT := DureeT + Duree;
  Numero := Numero + 1;

  //--- Affichage
  AfficherEntier (Numero, ZL_Num);
  Afficher (Titre, ZL_Titre);
  AfficherEntier (Duree, ZL_Duree);
  AfficherEntier (DureeT, ZT_DureeT);

end;

Le traitement consiste à :

  • lire le titre du morceau et sa durée ;
  • augmenter la durée totale (DureeT) par la durée du morceau ;
  • incrémenter le numéro du morceau ;
  • afficher le numéro, le titre et la durée du morceau dans les zones de liste prévues à cet effet ;
  • afficher la durée totale.

Mais que vaut la durée totale au départ ? Quel est le numéro du premier morceau ?

Cela dépend de la valeur de ces variables avant la première itération, c'est-à-dire au démarrage du programme.

Il faudrait donc pouvoir affecter des valeurs de départ (on dit initialiser) aux variables DureeT et Numero. Sinon, on ne pourra pas connaître leur valeur initiale.

Mais avec ce que nous avons vu jusqu'à présent, nous n'avons aucun moyen de faire exécuter des instructions à l'ordinateur avant le premier clic sur un bouton.

Heureusement, il existe une procédure évènementielle qui est appelée dès le démarrage du programme. Elle s'appelle Form_Create. Pour l'ajouter au code, il suffit de double-cliquer sur le fond du formulaire.

C'est donc dans la procédure Form_Create, qu'il faut initialiser les variables.

Voici la procédure Form_Create de notre exemple :

Sélectionnez
procedure TForm1.FormCreate(Sender: TObject);
begin
  //--- Initialisation
  DureeT := 0;
  Numero := 0;
end;

Comme cette procédure est déclenchée dès le démarrage du programme, nous saurons que la durée totale et le numéro seront nuls avant la première itération.

Exemple et conseils▲

L'objectif de la programmation n'est pas seulement de faire des programmes qui marchent.

En réalité, un programme n'est jamais terminé (du moins un programme volumineux tel que vous en rencontrerez en entreprise). Il faut donc pouvoir le modifier facilement, sachant que le développeur qui sera chargé de la mise à jour de votre programme ne sera pas forcément vous-même. Cela a deux conséquences :

  1. Votre code doit être lisible, c'est-à-dire qu'en le lisant on devine facilement ce qu'il fait. ;
  2. D'autre part, il doit être facilement modifiable.

Dans cette partie du cours, je vous donne à travers un exemple quelques conseils permettant de rendre un programme plus lisible et plus facile à modifier.

Mises à part la lisibilité et la facilité de modification, il existe d'autres critères importants en programmation que nous n'aborderons pas ici :

  • la rapidité d'exécution du code. Cet aspect est le sujet essentiel de l'algorithmique, qui peut être vue comme la science de résoudre un problème par un programme de manière à ce que ce programme s'exécute le plus rapidement possible. Sachez simplement que la manière de programmer peut jouer énormément sur la vitesse d'exécution ;
  • la concision du code, c'est-à-dire la résolution d'un problème avec un programme le plus court possible. Certains programmeurs sont capables d'écrire en deux lignes ce que d'autres écrivent en vingt lignes. Cet aspect est à mon avis moins important que les autres dans la mesure où un code concis est souvent peu lisible.

Exemple de programme et conseil▲

Cahier des charges▲

La réalisation d'un projet informatique démarre en général avec un cahier des charges. C'est en gros une description de ce que devrait faire le logiciel.

Voici le cahier des charges de notre exemple :

On souhaiterait écrire un programme qui calcule la surface à peindre dans une pièce rectangulaire possédant des portes et des fenêtres.

Pour simplifier, on supposera que les murs ont 2m50 de hauteur et que toutes les fenêtres ont la même dimension (1.4 × 1.2) et de même pour les portes (0.9 × 2.1).

Déclaration des constantes▲

Introduisons une constante pour chaque donnée fixe du problème :

Sélectionnez
   const
    LargeurFenetre = 1.4;
    HauteurFenetre = 1.2;
    LargeurPorte = 0.9;
    HauteurPorte = 2.1;
    HauteurMur = 2.5;

On pourrait se passer de ces déclarations et mettre directement les valeurs 1.4, 1.2, etc. dans les calculs, mais on obtiendrait ainsi un code moins lisible et plus difficile à mettre à jour.

S'il faut corriger la hauteur des portes par exemple, il suffit de changer la valeur de la constante et cette modification sera automatiquement répercutée dans tout le code (dans tous les calculs où la hauteur des portes intervient).

Essayez de procéder de cette manière dès que vous avez des valeurs fixées. Déclarez des constantes au lieu d'écrire les valeurs dans les instructions.

Conception de l'interface graphique▲

La surface à peindre dépendra donc de la taille de la pièce ainsi que du nombre de portes et de fenêtres. Ce seront les données du programme. On en déduit l'interface graphique suivante :

Image non disponible

Les zones de texte sont nommées ZT_Largeur (largeur de la pièce), ZT_Longueur (longueur de la pièce), ZT_NF (nombre de fenêtres), ZT_NP (nombre de portes) et ZT_SP (surface à peindre).

Les noms des zones de texte sont tous préfixés par ZT_. Cette manière de procéder permet d'éviter des conflits avec des noms de variables. En programmation, un même nom ne peut désigner qu'une seule chose. Vous ne pouvez pas, par exemple, nommer une zone de texte Largeur et utiliser par ailleurs une variable de même nom. Je vous propose de préfixer les noms des zones de texte par ZT_, ceux des zones de liste par ZL_ et ceux des boutons par BT_. Vous pouvez bien sûr avoir vos propres conventions. L'essentiel est d'en avoir une.

Lecture et déclaration des données▲

Associons une variable à chaque donnée : LargeurPiece, LongueurPiece, NFenetre et NPorte. D'où les déclarations :

Sélectionnez
 Var LargeurPiece, LongueurPiece : Double;
     NFenetre, NPorte : Integer;

Je n'ai pas besoin de vous expliquer ici la signification de chaque variable, car les noms choisis permettent de deviner la fonction de chacune d'entre elles. Pour obtenir un code lisible, il est recommandé de procéder de cette manière : utilisez des noms de variables parlants. Cette remarque est évidemment également valable pour les noms de constantes et pour, de manière générale, n'importe quel nom utilisé dans un programme.

Nous pouvons à présent écrire les instructions de lecture de données dans la procédure évènementielle du bouton Calculer :

Sélectionnez
procedure TForm1.BT_CalculClick(Sender: TObject);
begin
  // Lecture des données
  LireNombre (LargeurPiece, ZT_Largeur);
  LireNombre (LongueurPiece, ZT_Longueur);
  LireEntier (NFenetre, ZT_NF);
  LireEntier (NPorte, ZT_NP);
end;
Traitement, résultats intermédiaires et finals▲

Il nous faut maintenant réfléchir à la manière de calculer la surface à peindre. Pour simplifier le problème, nous allons le découper le traitement en plusieurs étapes :

  1. Calcul de la surface des murs ;
  2. Calcul de la surface des fenêtres ;
  3. Calcul de la surface des portes ;
  4. Calcul de la surface à peindre.

Le résultat de chaque étape sera sauvegardé dans une variable : SurfaceDesMurs, SurfaceDesFenetres, SurfaceDesPortes, SurfacePeinture.

Essayez également de procéder de cette manière lorsque vous avez un calcul compliqué. Découpez le calcul en plusieurs parties et sauvegardez le résultat de chacun de ces calculs dans une variable. À la fin, écrivez le calcul du résultat final en utilisant les résultats des calculs intermédiaires. Cette manière de programmer simplifie la résolution du problème et rend le programme plus lisible.

Il nous faut d'abord déclarer les nouvelles variables :

Sélectionnez
 Var LargeurPiece, LongueurPiece : Double;
     NFenetre, NPorte : Integer;
     SurfaceDesMurs, SurfaceDesPortes, SurfaceDesFenetres,
     SurfacePeinture : double;

Vous pouvez également procéder de cette manière. Il est en général difficile de prévoir dès le début toutes les variables que l'on aura besoin d'utiliser dans un programme. Ajoutez vos déclarations de variable au fur et à mesure des besoins.

Écrivons à présent les quatre étapes de calcul.

Calcul de la surface des quatre murs :

Sélectionnez
 SurfaceDesMurs :=
   2 * (LargeurPiece+LongueurPiece) * HauteurMur;

Calcul de la surface des fenêtres :

Sélectionnez
 SurfaceDesFenetres :=
  NFenetre * LargeurFenetre * HauteurFenetre;

Calcul de la surface des portes :

Sélectionnez
  SurfaceDesPortes :=
    NPorte * LargeurPorte * HauteurPorte;

Calcul de la surface à peindre :

Sélectionnez
 SurfacePeinture :=
   SurfaceDesMurs  - SurfaceDesFenetres - SurfaceDesPortes;
Le code de la procédure en entier▲

Pour conclure, voici le code de la procédure évènementielle du bouton Calculer :

Sélectionnez
procedure TForm1.BT_CalculClick(Sender: TObject);
begin
  //--- Lecture des données

  LireNombre (LargeurPiece, ZT_Largeur);
  LireNombre (LongueurPiece, ZT_Longueur);
  LireEntier (NFenetre, ZT_NF);
  LireEntier (NPorte, ZT_NP);

  //--- Traitement des données

  SurfaceDesMurs :=
   2 * (LargeurPiece+LongueurPiece) * HauteurMur;

  SurfaceDesFenetres :=
  NFenetre * LargeurFenetre * HauteurFenetre;

  SurfaceDesPortes :=
    NPorte * LargeurPorte * HauteurPorte;

  SurfacePeinture :=
   SurfaceDesMurs  - SurfaceDesFenetres - SurfaceDesPortes;

  // Affichage des résultats

  AfficherNombre (SurfacePeinture,ZT_SP);
end;

Le code de cette procédure est bien séparé en trois parties : lecture des données, traitement des données, puis affichage des résultats. Essayez également de procéder de cette manière. Écrivez votre code en séparant bien la lecture, le traitement et l'affichage.

Synthèse▲

Voici un résumé de tous les conseils vus précédemment.

  • Lorsque vous avez des valeurs fixées, utilisez des constantes.
  • Préfixez les noms des composants de l'interface graphique (suggestion : ZT_ = zone de texte, ZL_ = zone de liste, BT_ = bouton).
  • Utilisez des noms parlants.
  • Lorsque vous avez un calcul (ou plus généralement un traitement) compliqué, découpez-le en plusieurs parties et sauvegardez les résultats de chaque partie dans des variables.
  • Séparez bien la lecture des données, le traitement des données et l'affichage des résultats.

Exercices▲

Retrouvez différents exercices sur le site de l'auteur :

  • recherches complémentaires sur internet ;
  • prise en main de Lazarus ;
  • programme sans interface graphique ;
  • exercices de programmation ;
  • exercices complémentaires ;
  • exercices théoriques.

Pour obtenir les corrigés, le téléchargement n'est possible que via un login et un mot de passe, que vous pouvez obtenir en envoyant un mail à l'adresse suivante :

Image non disponible

en précisant un peu qui vous êtes et les raisons pour lesquelles ce cours vous intéresse.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Eric Thirion. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique Lazarus : Alcatîz - Contacter par email

Developpez.com

Nous contacter

Participez

Informations légales

Services

Forum Lazarus

Blogs

Hébergement

Partenaires

PlanetHoster
Ikoula

Copyright © 2000-2014 - www.developpez.com


Les bases de la programmation

  • Forums
  • Tutoriels
  • Magazine
  • FAQs
  • Blogs
  • Projets
  • Chat
  • Newsletter
  • Études
  • Emploi
  • Club
  • Contacts
  • Accueil Actualités IT Pro
  • ALM Cycle de vie du logiciel
    • ALM
    • Merise
    • UML
  • Java Plateforme et langage Java
    • Java
    • Java Web
    • Spring
    • Android
    • Eclipse
    • NetBeans
  • .NET Microsoft Framework .NET
    • Microsoft DotNET
    • Visual Studio
    • ASP.NET
    • C#
    • VB.NET
    • Windows Phone
    • Microsoft Azure
  • Dév. Web Développement Web et Webmarketing
    • Développement Web
    • AJAX
    • Apache
    • ASP
    • CSS
    • Dart
    • Flash / Flex
    • JavaScript
    • PHP
    • Ruby & Rails
    • Web sémantique
    • Webmarketing
    • (X)HTML
  • EDI Environnements de Développement Intégré
    • EDI
    • 4D
    • Delphi
    • Eclipse
    • LabVIEW
    • NetBeans
    • MATLAB
    • Scilab
    • Visual Studio
    • WinDev
    • Visual Basic 6
    • Lazarus
    • Qt Creator
  • Programmation Programmation et langages de programmation applicatifs
    • Programmation
    • Débuter - Algorithmique
    • 2D - 3D - Jeux
    • Assembleur
    • C
    • C++
    • Go
    • Objective C
    • Pascal
    • Perl
    • Python
    • Qt
    • XML
    • Autres
  • SGBD Systèmes de Gestion de Bases de Données
    • SGBD & SQL
    • 4D
    • Access
    • Big Data
    • DB2
    • Firebird
    • InterBase
    • MySQL
    • NoSQL
    • PostgreSQL
    • Oracle
    • Sybase
    • SQL-Server
  • Office Bureautique pour l'entreprise
    • Microsoft Office
    • Access
    • Excel
    • Word
    • Outlook
    • PowerPoint
    • SharePoint
    • Microsoft Project
  • Solutions d'entreprise Autres logiciels pour l'entreprise
    • Solutions d'entreprise
    • Big Data
    • Business Intelligence
    • ERP / PGI
    • CRM
    • SAS
    • Cloud Computing
    • SAP
    • Microsoft BizTalk Server
    • Talend
  • Applications Applications logicielles
    • Applications
    • Libres & Open Source
    • OpenOffice & LibreOffice
    • Projets
  • Mobiles Logiciels et matériels mobiles
    • Mobiles
    • Android
    • iOS
    • Windows Phone
  • Systèmes Logiciels et matériels systèmes
    • Systèmes
    • Windows
    • Linux Professionnel
    • Sécurité
    • PC
    • Mac
    • Réseau
    • Green IT
    • Virtualisation
    • Systèmes Embarqués
  • EDI
  • 4D
  • Delphi
  • Eclipse
  • LabVIEW
  • NetBeans
  • MATLAB
  • Scilab
  • Visual Studio
  • WinDev
  • Visual Basic 6
  • Lazarus
  • Qt Creator
  • Actualités Lazarus
  • Forum Lazarus
  • Tutoriels Lazarus
  • Tutoriels Pascal
  • F.A.Q. Lazarus
  • F.A.Q. Pascal
  • Sources Lazarus
  • Livres Lazarus

Les bases de la programmation

Table des matières Plier Déplier

  • Sous-programmes
    • But de ce chapitre
    • Introduction
      • Découpage d'un programme en sous-programmes
      • Sous-programmes non évènementiels
      • Les unités
      • Procédures et fonctions
    • Portée des variables
      • Exemple 1 (fichier : Global/ProjetGlobal.lpi)
      • Exemple 2 (fichier : Local0/ProjetLocal0.lpi)
      • Exemple 3 (fichier : Local1/ProjetLocal1.lpi)
      • Exemple 4 (fichier : Local2/ProjetLocal2.lpi)
      • Synthèse
        • Définition
        • Portée
        • Durée de vie
        • Déclarations locales multiples
        • Déclaration simultanée en local et en global
    • Exemple d'utilisation de sous-programmes
      • Utilisation des procédures
        • Exemple 1
        • Programmation descendante
        • Retour d'un sous-programme
        • Exemple 2
          • Détail technique
      • Exemple d'utilisation de fonctions
        • Ancienne version du projet
        • Nouvelle version : appel des fonctions
        • Nouvelle version : déclaration des fonctions
        • Retour du résultat
    • Principes généraux et compléments
      • Déclaration et appel de procédures
        • Déclaration d'une procédure
          • Entête
          • Variables locales
        • Écriture d'un appel de procédure
        • Exécution d'un appel de procédure
          • Principe du retour à la procédure appelante
          • Passage des paramètres et exécution de l'appel
      • Déclaration et appel de fonctions
        • Déclaration d'une fonction
        • Appel de fonction
          • Différences avec un appel de procédure
        • Les fonctions prédéfinies
        • Expressions et appels de fonctions
    • Exercices

Les sous-programmes existent dans tous les langages de programmation, depuis les origines. En effet, les développeurs se sont vite rendu compte que dans la programmation on retrouve très souvent des traitements similaires. Avec les sous-programmes, il devient possible de regrouper ces traitements en un seul bout de code réutilisable, ce qui permet une économie de temps considérable. D'autre part, l'utilisation de sous-programmes rend le code plus lisible et plus facile à mettre à jour. C'est donc une bonne manière de programmer.

Dans ce chapitre, nous verrons comment écrire et utiliser des sous-programmes. Nous verrons également qu'il est possible de déclarer des variables à l'intérieur d'un sous-programme et que ces variables, appelées variables locales, se différencient subtilement des variables globales que nous avons déjà rencontrées dans le premier cours.

Introduction▲

Le but de la programmation n'est pas seulement de faire des programmes qui marchent. Ils doivent également être lisibles, faciles à mettre à jour et plus concis.

C'est essentiellement à cela que servent les sous-programmes.

Découpage d'un programme en sous-programmes▲

Écrire du code à l'aide de sous-programmes est avant tout une manière de programmer : au lieu de concevoir un programme en un seul bloc, on le découpe en plusieurs parties aussi indépendantes que possible. Chacune de ces parties porte un nom : ce sont les sous-programmes.

Bien entendu, cette manière de programmer n'est pas obligatoire. On peut très bien programmer sans jamais écrire de sous-programmes, mais cela donne des programmes très longs, peu lisibles et difficiles à mettre à jour.

Par analogie, on pourrait comparer ceci au découpage d'une entreprise en plusieurs services. Chaque service ayant un rôle particulier à jouer dans l'entreprise. De même, on peut concevoir un programme en plusieurs sous-programmes réalisant chacun un traitement particulier.

Le PC est une autre analogie intéressante. Dans un PC, chaque partie est facilement remplaçable par une autre : vous pouvez remplacer l'écran par un autre écran, la carte son par une autre carte son, le clavier par un autre clavier, etc. Cette possibilité de remplacer chaque partie par une autre partie compatible est possible grâce à la conception modulaire du PC. Un programme sans sous-programme est comme un PC en un seul bloc : impossible d'interchanger les parties.

Sous-programmes non évènementiels▲

Dans le premier cours, nous avons déjà vu quelques sous-programmes.

Reprenons l'exemple du projet Addition.

Image non disponible

Lorsque l'utilisateur clique sur le bouton, la procédure évènementielle BoutonAdditionnerClick est automatiquement exécutée.

Cette procédure évènementielle est un exemple de sous-programme.

Une autre catégorie de sous-programmes, que j'appellerais sous-programmes non évènementiels, ne peuvent s'exécuter que s'il existe quelque part une instruction spéciale : l'appel de sous-programme.

De telles instructions figurent à l'intérieur de la procédure BoutonAdditionnerClick :

Sélectionnez
procedure TForm1.BoutonAdditionnerClick(Sender: TObject);
begin
  LireEntier (x,zoneTexteX);              { <-- }
  LireEntier (y,ZoneTexteY);              { <-- }
  somme := x + y;
  AfficherEntier (somme, ZoneTexteSomme); { <-- }
end;

LireEntier et AfficherEntier sont en fait des sous-programmes (non évènementiels). Dans cet exemple, les trois instructions marquées d'une flèche sont des appels de sous-programme. LireEntier est donc appelé deux fois et AfficherEntier, une fois.

Par contre, le code du projet Addition ne contient aucun appel du sous-programme BoutonAdditionnerClick. Comme il s'agit d'un sous-programme évènementiel, son exécution sera déclenchée par un évènement ; en l'occurrence, un clic sur le bouton Additionner.

Évidemment, l'exécution des sous-programmes LireEntier et AfficherEntier est également déclenchée, mais de manière indirecte. Si BoutonAdditionClick ne contenait pas des instructions d'appel de ces procédures, elles ne seraient jamais exécutées !

Les unités▲

Un projet Lazarus est généralement composé de plusieurs fichiers source Pascal appelés unités.

Dans le projet Addition, par exemple, nous avons deux modules :

  • l'unité UnitAddition, qui contient la procédure évènementielle du bouton Additionner ;
  • l'unité entrees_sorties, qui contient le code des procédures Lire, Afficher, etc.

De manière générale, chaque formulaire est associé à une unité contenant les procédures évènementielles. C'est l'unité qui gère l'interface graphique du programme. Dans notre exemple, c'est l'unité UnitAddition. Mais il peut y avoir plusieurs autres unités contenant le code de sous-programmes non évènementiels. Dans notre exemple, il n'y a qu'une seule unité de ce type. C'est l'unité entrees_sorties.

Procédures et fonctions▲

Jetons un Âœil dans le fichier entrees_sorties. On y trouve, par exemple, le code du sous-programme AfficherEntier que voici :

Sélectionnez
procedure AfficherEntier(x: integer;c:TObject);
begin
if c is TEdit then
 (c as TEdit).Text:=IntToStr(x)
else
 if c is TListBox then
   (c as TListBox).Items.Add(IntToStr(x))
 else
 begin
   ShowMessage('Erreur dans la fonction Afficher :
                type du 2e paramètre non valide');
 end;
end;

Pour l'instant, vous ne comprenez pas grand-chose à ce code, car il fait appel à plein de notions que vous n'avez pas encore vues (structures de contrôles, programmation objet). Notez simplement que la première ligne commence par le mot-clé procedure, suivi du nom du sous-programme. Cela signifie que le sous-programme appartient à la catégorie des procédures.

À droite du nom de la procédure, on trouve les paramètres. Dans cet exemple, il y a deux paramètres : x de type integer et c de type TObject.

Toute la première ligne du sous-programme constitue l'entête : elle contient donc le nom du sous-programme et ses éventuels paramètres. Tout ce qui suit est le corps du sous-programme. À l'intérieur du corps du sous-programme figurent donc les instructions que le sous-programme doit exécuter.

Les paramètres sont les données d'un sous-programme. Dans le cas de la procédure AfficherEntier, x est le nombre à afficher et c le composant de l'interface graphique (zone de texte ou zone de liste) dans lequel on souhaite afficher x.

Nous reviendrons plus en détail sur cette notion de paramètres. Pour l'instant, sachez que tout sous-programme peut avoir des paramètres ou ne pas en avoir.

D'autres sous-programmes du fichier commencent par le mot-clé function. Ce sont les fonctions. C'est par exemple le cas du sous-programme ZoneDeTexteVide :

Sélectionnez
function ZoneDeTexteVide (zt: TEdit) : boolean;
begin
  ZoneDeTexteVide := zt.Text = '';
end;

Nous avons donc deux catégories de sous-programmes : les procédures et les fonctions. Les différences entre ces deux catégories seront vues dans la suite du cours.

Portée des variables▲

Une variable peut être de portée locale ou globale. Elle est globale si elle est déclarée en dehors de tout sous-programme. Au contraire, une variable locale est déclarée à l'intérieur d'un sous-programme.

Mais pour l'instant, nous n'avons pas défini ce qu'est un sous-programme de manière générale. Par contre, vous savez ce qu'est une procédure évènementielle. Or, les procédures évènementielles représentent une catégorie particulière de sous-programme.

Nous allons donc, pour l'instant, baser nos explications concernant la portée des variables en utilisant uniquement les procédures évènementielles. Gardez toutefois à l'esprit que ces explications seront valables pour n'importe quel type de sous-programme.

Les variables locales et globales ne se comportent pas de la même manière, notamment en ce qui concerne leur durée de vie.

Pour illustrer la différence entre une variable locale et une variable globale, nous commencerons par quelques exemples de programmes, que vous pouvez télécharger sur le site de l'auteur :

  • exemple 1 ;
  • exemple 2 ;
  • exemple 3 ;
  • exemple 4.

Vous pouvez donc les ouvrir et les exécuter pour vous aider à mieux comprendre ou pour vous convaincre.

Exemple 1 (fichier : Global/ProjetGlobal.lpi)▲

Voici le formulaire du programme :

Image non disponible

et voici un extrait du code de ce programme :

Sélectionnez
var
  Form1: TForm1;
  x : integer;

implementation

procedure TForm1.BoutonAffecterClick(Sender: TObject);
begin
  x := 1961;
end;

procedure TForm1.BoutonAfficherClick(Sender: TObject);
begin
  AfficherEntier (x, ZoneTexteX);
end;

Dans ce premier exemple, la variable x est déclarée à l'extérieur de tout sous-programme. Il s'agit donc d'une variable globale.

Cela a les conséquences suivantes :

  • x peut être utilisée dans tout le fichier de code où elle est déclarée et, par conséquent, à l'intérieur de toutes les procédures définies dans ce fichier. Dans notre exemple, la variable x est utilisée dans les deux procédures évènementielles BoutonAffecterClick et BoutonAfficherClick. La procédure BoutonAffecterClick affecte la valeur 1961 à x et la procédure BoutonAfficherClick affiche la valeur de x ;
  • x a une durée de vie égale à celle de l'exécution du programme : dès le démarrage du programme, de l'espace mémoire est alloué à la variable x. Lorsque le programme s'arrête, cet espace mémoire n'est plus réservé à la variable et on peut donc considérer qu'elle cesse d'exister. Pour vous en convaincre, lancez le programme, affectez la valeur 1961 à x. Vérifiez que x a bien la valeur 1961. Arrêtez le programme et relancez-le. Vous constaterez que x n'a plus la valeur 1961.

Exemple 2 (fichier : Local0/ProjetLocal0.lpi)▲

Déclarons cette fois-ci la variable x à l'intérieur de la procédure BoutonAffecterClick. On obtient le résultat suivant :

Image non disponible

Lazarus affiche un message d'erreur Identifier not found « x ». Cette erreur est localisée dans l'instruction AfficherEntier(x, ZoneTexteX) de la procédure BoutonAfficherClick.

Lazarus prétend ne pas connaître l'identificateur x dont on parle dans cette instruction.

Or nous avons bien déclaré x !

Comment expliquer cette erreur ?

Le problème vient du fait qu'une variable locale n'est accessible qu'à l'intérieur du sous-programme où elle est déclarée. Autrement dit, la portée d'une variable locale est limitée à ce sous-programme.

Dans notre exemple, x n'est donc définie qu'à l'intérieur de la procédure BoutonAffecterClick et nulle part ailleurs.

Exemple 3 (fichier : Local1/ProjetLocal1.lpi)▲

Voici à présent une version légèrement modifiée du programme précédent, dans laquelle la variable x est déclarée au début du programme ainsi qu'à l'intérieur de la procédure BoutonAffecterClick :

Sélectionnez
var
  Form1: TForm1;
  x : integer;

implementation

procedure TForm1.BoutonAffecterClick(Sender: TObject);
var x : integer;
begin
  x := 1961;
end;

procedure TForm1.BoutonAfficherClick(Sender: TObject);
begin
  AfficherEntier (x, ZoneTexteX);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  x := 1935;
  AfficherEntier (x, ZoneTexteX);
end;

Cet exemple ne provoque aucun message d'erreur.

Vous vous demandez peut-être comment une telle chose est possible, car a priori une variable ne peut pas être déclarée deux fois. On devrait avoir un message d'erreur. Or il n'en est rien !

En réalité, il y a deux variables distinctes : la variable x déclarée en global et celle déclarée en local dans la procédure BoutonAffecterClick.

Bien qu'elles portent le même nom, ces deux variables sont totalement indépendantes.

Mais si Lazarus rencontre une instruction utilisant x, comment peut-il savoir de quelle variable il s'agit ?

Il s'en sort de la manière suivante :

  • l'affectation x := 1961, dans BoutonAffecterClick doit agir sur la variable locale x car x est défini dans cette procédure ;
  • par contre, l'instruction AfficherEntier(x, ZoneTexteX) dans BoutonAfficherClick agit sur la variable globale x car cette procédure ne contient aucune déclaration de la variable x ;
  • de même, les instructions x := 1935 et AfficherEntier (x, ZoneTexteX) dans FormCreate agissent sur la variable globale.

De manière générale :

La déclaration locale est prioritaire sur la déclaration globale.

Autrement dit, si une variable globale est redéclarée à l'intérieur de certains sous-programmes alors :

  • à l'intérieur de ces sous-programmes, c'est la déclaration locale qui l'emporte ;
  • à l'intérieur d'un sous-programme dans laquelle cette variable n'est pas redéclarée, c'est la déclaration globale qui est prise en compte.

À présent, faites l'expérience suivante :

  1. lancez le programme. Il affiche la valeur 1935 pour x ;
  2. cliquez sur le bouton Affecter la valeur 1961 à X, puis sur Afficher X. Résultat : la variable x n'a pas été modifiée !

Comment expliquer ceci ?

Lorsque vous lancez le programme, Lazarus exécute tout d'abord la procédure FormCreate. Celle-ci affecte la valeur 1935 à la variable x déclarée en globale, puis affiche la valeur de cette même variable.

Lorsque vous cliquez sur le bouton Affecter la valeur 1961 à X, il exécute la procédure BoutonAffecterClick, dans laquelle x est déclarée en local. Cette même procédure affecte la valeur 1961 à x. Mais comme x est déclarée dans cette procédure, cette affectation agit sur la variable x déclarée dans cette procédure et non pas sur la variable x déclarée en global.

Enfin, lorsque vous cliquez sur le bouton Afficher X, il exécute la procédure BoutonAfficherClick, dans laquelle ne figure aucune déclaration locale de la variable x. Cette procédure affiche donc la valeur de la variable globale x qui est toujours égale à 1935.

Exemple 4 (fichier : Local2/ProjetLocal2.lpi)▲

Cette fois-ci, nous déclarons x en local dans toutes les procédures, mais pas en global :

Sélectionnez
var
  Form1: TForm1;

implementation

procedure TForm1.BoutonAffecterClick(Sender: TObject);
var x : integer;
begin
  x := 1961;
end;

procedure TForm1.BoutonAfficherClick(Sender: TObject);
var x : integer;
begin
  AfficherEntier (x, ZoneTexteX);
end;

procedure TForm1.FormCreate(Sender: TObject);
var x : integer;
begin
  x := 1935;
  AfficherEntier (x, ZoneTexteX);
end;

Nous avons en réalité ici trois variables locales x indépendantes : celle de la procédure BoutonAffecterClick, celle de la procédure BoutonAfficherClick et celle de la procédure FormCreate.

Même expérience qu'avant :

  1. lancez le programme. Il affiche la valeur 1935 pour x ;
  2. cliquez sur le bouton Affecter la valeur 1961 à X, puis sur Afficher X. La valeur affichée est (très très probablement) différente de 1961.

Explications :

Lorsque vous lancez le programme, Lazarus exécute FormCreate. Ce sous-programme affecte la valeur 1935 à la variable locale x, puis affiche la valeur de cette même variable.

Lorsque vous cliquez sur le bouton Affecter la valeur 1961 à X, il exécute le sous-programme BoutonAffecterClick, qui affecte la valeur 1961 à la variable locale x de cette procédure.

Enfin, lorsque vous cliquez sur le bouton Afficher X, il exécute le sous-programme BoutonAfficherClick, qui affiche la valeur de la variable locale x de cette procédure. Comme aucune valeur n'est affectée à x dans cette procédure, cette variable a donc une valeur indéfinie.

Synthèse▲

Voilà en résumé ce qu'il faut retenir sur les variables locales et globales :

Définition▲

Par définition, une variable est locale si elle est déclarée à l'intérieur d'un sous-programme. Elle est globale dans le cas contraire.

Portée▲

Une variable globale est utilisable par tous les sous-programmes contenus dans le fichier où elle est déclarée.

Une variable locale, au contraire, n'est utilisable qu'à l'intérieur du sous-programme dans lequel elle est déclarée.

Durée de vie▲

La durée de vie d'une variable globale est égale à celle du programme. La zone mémoire allouée pour cette variable lui reste allouée tant que le programme s'exécute.

La durée de vie d'une variable locale est celle d'une exécution du sous-programme dans lequel elle est déclarée. Elle peut donc avoir plusieurs vies : elle renaît chaque fois que le sous-programme s'exécute et meurt chaque fois qu'il se termine.

Déclarations locales multiples▲

Si une « même » variable est déclarée dans plusieurs sous-programmes, tout se passe comme si on avait donné des noms de variables distincts pour ces différentes déclarations. Autrement dit, chaque déclaration engendre une variable totalement indépendante des autres.

Déclaration simultanée en local et en global▲

Si une « même » variable est déclarée en local et en global :

  • dans les sous-programmes où cette variable n'est pas déclarée, c'est la déclaration globale qui est prise en compte ;
  • à l'intérieur des sous-programmes où cette variable est déclarée, c'est la déclaration locale qui est prise en compte.

Exemple d'utilisation de sous-programmes▲

Utilisation des procédures▲

Exemple 1▲

Concevoir un programme à l'aide de sous-programmes signifie découper un traitement en sous-traitements spécifiques. Pour illustrer ceci, reprenons le projet Peinture du cours Premières notions.

Image non disponible

Rappelons que ce programme permet de calculer la surface à peindre d'une pièce rectangulaire, connaissant les dimensions de la pièce (longueur, largeur), le nombre de fenêtres et le nombres de portes.

Cet exemple nous avait justement servi à illustrer le principe du découpage d'un traitement en sous-traitements. Lorsqu'un calcul est trop complexe, on a intérêt à le découper en plusieurs calculs intermédiaires et sauvegarder les résultats intermédiaires dans des variables.

Cela nous avait donné le code suivant pour le bouton Calculer :

Image non disponible

Nous avons ici un exemple de code qui peut très facilement se réécrire à l'aide de sous-programmes. Associons un appel de procédure à chaque traitement, cela nous donne :

Image non disponible

Comparez ce code à celui de la version précédente : il est devenu plus court et plus lisible.

Écrivons à présent le code des procédures de calcul. Ce code doit être écrit avant la procédure évènementielle BT_CalculClick. Il suffit d'écrire une entête pour chaque procédure, puis de copier en-dessous l'affectation correspondante enrobée de begin end. On obtient :

Image non disponible

Programmation descendante▲

Remarquez que l'on aurait pu procéder ainsi dès le départ : notre problème est de calculer la surface à peindre. Ce problème peut être décomposé en quatre sous-problèmes : calculer la surface des murs, la surface des fenêtres, la surface des portes et finalement la surface à peindre.

On associe à chaque problème une procédure, ce qui nous permet d'écrire le code du programme sans nous occuper des détails, c'est-à-dire avant même d'avoir résolu les sous-problèmes.

Image non disponible

Cette manière de procéder est appelée la programmation descendante.

Retour d'un sous-programme▲

Lorsqu'une procédure évènementielle a fini de s'exécuter, le programme retourne automatiquement à un état d'attente d'évènement.

Dans le cas d'un sous-programme non évènementiel, c'est un peu différent : l'ordinateur revient à l'instruction qui suit l'appel. Dans le cas du projet Peinture, on obtient ceci :

Image non disponible

Exemple 2▲

Dans le premier exemple d'utilisation des procédures, nous avons réécrit le projet Peinture à l'aide de procédures sans paramètres.

L'exemple présenté ici introduit les procédures avec paramètres. Il s'agit d'un programme (sans intérêt autre que pédagogique !) qui permet d'afficher le mail d'une personne de nom et prénom donnés :

Image non disponible

Le bouton SFR affiche le mail chez SFR, alors que le bouton FREE affiche le mail chez Free. Voici le code de la procédure évènementielle associée au bouton FREE :

Image non disponible

On constate que le code des deux procédures évènementielles associées aux boutons SFR et FREE se ressemble énormément. La seule chose qui change est le nom de l'opérateur.

Pour transformer ces deux traitements quasiment identiques en un seul traitement, on peut utiliser un sous-programme paramétré par le nom de l'opérateur. Voici le code de ce sous-programme :

Sélectionnez
procedure AfficherLeMailChez (operateur : string);
var prenom, nom : string;
begin
Lire (prenom, Form1.ZT_Prenom);
Lire (nom, Form1.ZT_Nom);
Afficher (prenom+'.'+nom+'@'+operateur+'.fr', Form1.ZT_Mail);
end;

Le code des deux procédures évènementielles se résume alors à l'appel de cette procédure avec deux valeurs de paramètres différents :

Sélectionnez
procedure TForm1.BT_SFRClick(Sender: TObject);
begin
 AfficherLeMailChez ('sfr');
end;

procedure TForm1.BT_FREEClick(Sender: TObject);
begin
  AfficherLeMailChez ('free');
end;

Les valeurs des paramètres à l'appel ('sfr' et 'free' dans notre exemple) sont les paramètres effectifs à ne pas confondre avec les paramètres formels : ce sont les paramètres déclarés dans l'entête de la procédure. Dans notre exemple, il n'y a qu'un seul paramètre formel (operateur de type string).

Détail technique▲

Un petit détail technique pour terminer la présentation de cet exemple. Vous remarquerez que dans le code de la procédure AfficherLeMailChez, les zones de texte sont toutes préfixées par Form1 :

Sélectionnez
procedure AfficherLeMailChez (operateur : string);
var prenom, nom : string;
begin
Lire (prenom, Form1.ZT_Prenom);
Lire (nom, Form1.ZT_Nom);
Afficher (prenom+'.'+nom+'@'+operateur+'.fr', Form1.ZT_Mail);
end;

De manière générale, pour accéder aux composants d'une interface graphique depuis un sous-programme non évènementiel, il est nécessaire de préfixer les noms de ces composants par le nom du formulaire.

À ce stade, nous ne pouvons pas expliquer pourquoi cela est nécessaire car l'explication fait appel à des notions de programmation objet.

Exemple d'utilisation de fonctions▲

L'exemple présenté ici reprend le projet Peinture en l'écrivant avec des fonctions. Ce nouvel exemple se trouve dans le répertoire Exemple-Sous-Programme/PeintureAvecFonctions, que vous pouvez télécharger sur le site de l'auteur.

Image non disponible

Rappelons que le projet Peinture permet de calculer la surface à peindre d'une pièce rectangulaire, connaissant les dimensions de la pièce (longueur, largeur), le nombre de fenêtres et le nombre de portes.

Les dimensions des fenêtres, des portes et la hauteur des murs sont des constantes (LargeurFenetre=1,4 ;
                            HauteurFenetre=1,2 ; LargeurPorte=0,9 ;
                            HauteurPorte=2,1 ; HauteurMur=2,5).

Ancienne version du projet▲

La dernière version (répertoire Exemple-Sous-Programme/Peinture) nous avait servi à introduire les procédures. Nous n'avions alors aucune notion de variables locales, ni de paramètres. Cette version du projet avait donc été écrite en utilisant uniquement des variables globales et des procédures sans paramètre.

Pour calculer la surface à peindre à l'aide de procédures, nous avions procédé par programmation descendante en découpant le problème en quatre sous-problèmes :

  1. calcul de la surface des murs ;
  2. calcul de la surface des fenêtres ;
  3. calcul de la surface des portes ;
  4. calcul de la surface à peindre.

Nous avions associé une procédure à chacun de ces traitements. Voici le code de ces procédures :

Image non disponible

et voici l'appel de ces procédures dans la procédure évènementielle associée au bouton Calculer :

Image non disponible

Nouvelle version : appel des fonctions▲

La nouvelle version que nous allons présenter ici n'utilise au contraire aucune variable globale et effectue des calculs par l'intermédiaire de fonctions.

Voici le code de cette même procédure évènementielle dans la nouvelle version :

Sélectionnez
procedure TForm1.BT_CalculClick(Sender: TObject);
var LargeurPiece, LongueurPiece, Surface_A_Peindre : double;
   NFenetre, NPorte : Integer;
begin
LireNombre (LargeurPiece, ZT_Largeur);
LireNombre (LongueurPiece, ZT_Longueur);
LireEntier (NFenetre, ZT_NF);
LireEntier (NPorte, ZT_NP);

Surface_A_Peindre :=
  SurfaceDesMurs(LargeurPiece,LongueurPiece)
  - SurfaceDesFenetres(NFenetre)
  - SurfaceDesPortes(Nporte);

AfficherNombre (Surface_A_Peindre,ZT_SP);
end;

Le calcul de la surface à peindre se fait à présent en une seule affectation. Plus précisément, il est représenté par l'expression qui appelle trois fonctions :

  1. SurfaceDesMurs : cette fonction calcule la surface des murs à partir des dimensions de la pièce ;
  2. SurfaceDesFenetres : elle calcule la surface des fenêtres à partir du nombre de fenêtres ;
  3. SurfaceDesPortes : idem pour les portes à partir du nombre de portes.
Nouvelle version : déclaration des fonctions▲

Voici le code de ces trois fonctions :

Image non disponible

L'entête d'une fonction commence nécessairement par le mot clé function. Ce dernier est suivi du nom de la fonction puis, optionnellement, de la liste des paramètres. Enfin, l'entête se termine par le type du résultat produit par la fonction. Dans notre cas, il s'agit du type double, car le résultat produit est un nombre a priori non entier.

Une fonction se distingue d'une procédure par le fait qu'elle retourne un résultat. Pour comprendre ce que signifie « retourner un résultat », il faut voir comment l'ordinateur exécute une expression contenant des appels de fonctions. Nous reviendrons là-dessus un peu plus loin.

Dans notre exemple, chaque fonction ne contient qu'une seule instruction. Ce n'est pas vrai en général. Le corps d'une fonction peut contenir plusieurs instructions. Comment savoir alors quel est le résultat de la fonction ? En Pascal, le résultat d'une fonction est déterminée par la dernière exécution d'une affectation de la forme :

Sélectionnez
  Nom de la fonction := expression ;

Il faut donc nécessairement qu'une affectation de ce type soit présente dans le corps de la fonction afin que le résultat retourné par celle-ci soit défini.

Vous constaterez que, dans notre exemple, chaque fonction contient bien une instruction de ce type.

Retour du résultat▲

Voyons maintenant ce que signifie « retourner un résultat ». Pour cela, nous allons voir comment est évaluée l'expression contenant les appels de fonctions :

Sélectionnez
  SurfaceDesMurs(LargeurPiece,LongueurPiece)
  - SurfaceDesFenetres(NFenetre)
  - SurfaceDesPortes(Nporte);

L'expression est évaluée de gauche à droite. L'ordinateur va donc commencer par évaluer l'appel de la fonction SurfaceDesMurs. Supposons que LargeurPiece = 3,6 et LongueurPiece = 4,3.

Comme dans les appels de procédures, les paramètres formels sont remplacés par les paramètres effectifs. Donc ici, LaP va prendre la valeur 3,6 et LoP, la valeur 4,3. Puis le corps de la fonction est exécuté avec ces valeurs. La fonction effectue donc le calcul suivant :

Sélectionnez
  SurfaceDesMurs :=  2 * (3,6 + 4,3) * HauteurMur;

Avec une hauteur de mur de 2,50 m, cela fait 39,5 m2.

À la fin de l'exécution de la fonction, ce résultat va « retourner » dans l'expression qui l'a appelée. Tout se passe comme si la valeur 39,5 remplaçait à présent l'appel de fonction dans cette expression. c'est-à-dire qu'il faut à présent évaluer l'expression :

Sélectionnez
  39,5  - SurfaceDesFenetres(NFenetre)
  - SurfaceDesPortes(Nporte);

Mais l'évaluation de l'expression n'est pas terminée. L'ordinateur va à présent exécuter l'appel de la fonction SurfaceDesFenetres. Supposons que le nombre de fenêtres soit égal à trois ; nf, le paramètre formel de cette fonction, va donc prendre la valeur 3 et la fonction va faire le calcul suivant :

Sélectionnez
  SurfaceDesFenetres := 3 * LargeurFenetre * HauteurFenetre;

Avec une largeur de fenêtre de 1,4 m et une hauteur de fenêtre de 1,2 m, cela donne 5,04 m2.

Celle valeur retourne dans l'expression d'appel et nous obtenons :

Sélectionnez
39,5 - 5,04 - SurfaceDesPortes(Nporte);

Ici, l'ordinateur va d'abord effectuer la différence 39,5 - 5,04. Cela fait 34,46. Nous nous retrouvons donc avec l'expression :

Sélectionnez
34,46 - SurfaceDesPortes(Nporte);

Il reste donc à évaluer l'appel de la fonction SurfaceDesPortes. Supposons deux portes ; le paramètre formel np prend la valeur 2 et la fonction effectue le calcul suivant :

Sélectionnez
   SurfaceDesPortes := 2 * LargeurPorte * HauteurPorte;

Avec une largeur de porte de 0,9 m et une hauteur de porte de 2,1 m, cela fait 3,78 m2. En ramenant ce résultat dans l'expression de départ nous obtenons :

Sélectionnez
34,46 - 3,78;

Ce qui fait 30,68 m2. Vous pouvez vérifier que c'est bien le résultat affiché par le programme :

Image non disponible

Principes généraux et compléments▲

Déclaration et appel de procédures▲

Déclaration d'une procédure▲
Sélectionnez
procedure NomDeLaProcédure (liste des paramètres);
  Déclaration des variables locales
begin
  Instructions
end;

La première ligne est l'entête de la procédure, formée du mot-clé procedure suivi du nom de la procédure et d'une liste optionnelle de paramètres entre parenthèses.

La partie entre begin et end est le corps de la procédure.

Entête▲

Sous sa forme la moins compacte, la liste des paramètres s'écrit :

Sélectionnez
   paramètre1 : type1,...., paramètreN : typeN

On peut obtenir une écriture plus concise en regroupant les paramètres consécutifs de même type, mais dans ce cas chaque groupe de paramètres doit être séparé du suivant par un point virgule, sachant qu'un groupe de paramètres s'écrit :

Sélectionnez
   paramètre1,...., paramètreN : type
Variables locales▲

Toute procédure n'utilise pas nécessairement des variables locales, mais si c'est le cas, elles doivent être déclarées entre l'entête et le premier begin.

Écriture d'un appel de procédure▲

L'appel d'une procédure est formé du nom de cette procédure suivi d'une liste de paramètres effectifs entre parenthèses.

Sélectionnez
   NomDeLaProcédure (liste des paramètres effectifs);

Dans l'exemple que nous avons présenté, les paramètres effectifs étaient des chaînes de caractères. De manière générale, la liste des paramètres effectifs est constituée d'expressions quelconques séparées par des virgules :

Sélectionnez
   expression1,...., expressionN

Pour que l'appel de procédure soit compilable (et exécutable !), le type de ces expressions doit être compatible avec celui des paramètres formels :

  • il doit y avoir autant de paramètres effectifs que de paramètres formels ;
  • le type d'un paramètre effectif quelconque doit être compatible avec celui du paramètre formel de même position.

Voici par exemple une procédure qui possède trois paramètres formels (p, n ,o) de type chaîne de caractères :

Sélectionnez
procedure FormerMail (p, n , o : string);
begin
    mail := p+'.'+n+'@'+o+'.fr';
end;

En supposant que Prenom1, Prenom2 et Nom soient des variables de type chaîne de caractères, elle pourrait être appelée de la manière suivante :

Sélectionnez
         FormerMail (Prenom1 + '-' + Prenom2, Nom, 'free');

Le premier paramètre effectif est une expression de type chaîne de caractères, le deuxième une variable de type chaîne de caractères et le troisième, un littéral de type chaîne de caractères. Ces trois paramètres effectifs sont donc compatibles avec les paramètres formels.

Par contre, elle ne pourra être appelée d'aucune des manières suivantes :

Sélectionnez
    FormerMail (Prenom1, Nom);
Sélectionnez
    FormerMail (Prenom1, Nom, 9);

En effet, dans le premier appel, le nombre de paramètres effectifs n'est pas égal au nombre de paramètres formels et dans le deuxième, le type du troisième paramètre effectif (numérique) n'est pas compatible avec le type du troisième paramètre formel.

Exécution d'un appel de procédure▲
Principe du retour à la procédure appelante▲

Un appel de procédure est toujours contenu dans une autre procédure, que nous appellerons la procédure appelante.

Lorsqu'un appel de procédure est exécuté, le processeur interrompt momentanément l'exécution de la procédure appelante pour aller exécuter la procédure appelée. Après avoir exécuté les instructions de cette procédure, il reprend l'exécution de la procédure appelante à partir de l'instruction qui suivait l'appel.

Image non disponible

Passage des paramètres et exécution de l'appel▲

Les paramètres formels d'un sous-programme peuvent être considérés comme des variables locales.

L'exécution d'un appel de procédure consiste à :

  • évaluer chacun des paramètres effectifs ;
  • allouer de la mémoire pour les paramètres formels ;
  • affecter à chaque paramètre formel la valeur du paramètre effectif correspondant ;
  • exécuter le code de la procédure avec ces valeurs de paramètres ;
  • libérer l'espace mémoire alloué aux paramètres formels.

Reprenons l'exemple de la procédure FormerMail appelée de la manière suivante :

Sélectionnez
         FormerMail (Prenom1+'-'+Prenom2, Nom, 'free');

Image non disponible

En supposant que les variables Prenom1, Prenom2 et Nom aient respectivement pour valeur 'Jean', 'Sebastien' et 'Bach', cet appel donnerait lieu aux opérations suivantes :

  • évaluation des paramètres effectifs :
    • valeur de l'expression Prenom1 + '-' + Prenom2 : 'Jean-Sebastien' ;
    • valeur de l'expression Nom : 'Bach' ;
    • valeur de l'expression 'Free' : 'Free'.
  • allocation mémoire : de la mémoire est allouée aux trois paramètres formels p, n et o ;
  • affectation des valeurs aux paramètres formels :
    • la valeur 'Jean-Sebastien' est affectée au paramètre p ;
    • la valeur 'Bach' est affectée au paramètre n ;
    • la valeur 'Free' est affectée au paramètre o.
  • exécution du code de la procédure : l'instruction
    mail := p + '.' + n + '@' + o + '.fr';
    est exécutée avec ces valeurs de paramètres ;
  • libération de place mémoire : la place mémoire allouée aux paramètres p, n et o est libérée.

Déclaration et appel de fonctions▲

Déclaration d'une fonction▲
Sélectionnez
function NomDeLaFonction (liste des paramètres) : type;
  Déclaration des variables locales
begin
  Instructions (dont une instruction de retour)
end;

Différences avec la déclaration d'une procédure :

  • l'entête commence par le mot-clé function :
  • l'entête se termine par le type du résultat retourné par la fonction ;
  • parmi les instructions contenues dans le corps de la fonction doit figurer une instruction permettant de retourner le résultat. En Pascal, cette instruction s'écrit :
Sélectionnez
NomDeLaFonction := expression;
Appel de fonction▲

Un appel de fonction s'écrit :

Sélectionnez
NomDeLaFonction (liste des paramètres effectifs)

Comme pour les procédures, le nombre de paramètres effectifs doit être égal au nombre de paramètres formels et le type d'un paramètre effectif quelconque doit être compatible avec celui du paramètre formel de même position.

Différences avec un appel de procédure▲

Un appel de fonction est une expression dont la valeur est celle retournée par la fonction. Un appel de procédure, au contraire, n'est pas une expression mais une instruction. L'exécution d'une procédure ne retourne aucune valeur.

Une appel de fonction figure en général à l'intérieur d'une instruction ou d'une expression. Par exemple, si Moyenne est une fonction calculant la moyenne de deux nombres, on peut écrire :

Sélectionnez
    m:= Moyenne (A,B);

    AfficherNombre (Moyenne (A,B), ZT_Moy)

    (Moyenne(A,B) + Moyenne(C,D)) / 2

Un appel de procédure, au contraire, ne peut pas figurer à l'intérieur d'une instruction, ni d'une expression.

Les fonctions prédéfinies▲

Comme tous les langages, le Pascal offre une multitude de fonctions prédéfinies. Ces fonctions réalisent en général des opérations très fréquemment utilisées afin de faciliter la tâche du programmeur. On y trouve en particulier les fonctions mathématiques (exemples) et les fonctions de manipulation de chaîne de caractères (exemples).

En Pascal, elles sont réparties dans différentes unités de code. Pour pouvoir les utiliser dans une unité d'un projet, le nom de ces unités doit figurer dans la liste des unités importées, c'est-à-dire après uses.

Par défaut, Lazarus importe huit unités dans une unité associée à un formulaire :

Image non disponible

Comme toutes les fonctions prédéfinies ne figurent pas dans ces huit unités, il faut dans certains cas étendre la liste des unités importées.

Prenons par exemple la fonction floor. Cette fonction retourne la partie entière d'un nombre. Elle figure dans l'unité Math.pas (fonctions mathématiques). Pour pouvoir utiliser floor dans une unité, il faut donc ajouter Math dans la liste des unités importées par celle-ci :

Image non disponible

Expressions et appels de fonctions▲

Comme nous l'avons déjà dit, un appel de fonction est une expression dont la valeur est celle retournée par la fonction.

Cela nous permet d'élargir la notion d'expression valide abordée dans le premier cours.

Pour cela, il nous suffit de rajouter la règle suivante :

Un appel de fonction est une expression valide de type T, si les paramètres effectifs sont compatibles avec les paramètres formels de la fonction et que cette fonction retourne un résultat de type T.

Cette nouvelle règle, en conjonction avec les règles déjà vues, nous permet de construire des expressions faisant intervenir des fonctions. Voici quelques exemples utilisant les fonctions prédéfinies :

Expression | Type | Valeur
10+Random(2) | Integer | 10, 11 ou 12
'Age:' + IntToStr(51) | String | 'Age : 51'
LeftStr('Thirion',Length('Thirion')-1) | String | 'Thirio'

Exercices▲

Retrouvez différents exercices sur le site de l'auteur :

  • exercices sous Lazarus ;
  • exercices sur papier.

Pour obtenir les corrigés, le téléchargement n'est possible que via un login et un mot de passe, que vous pouvez obtenir en envoyant un mail à l'adresse suivante :

Image non disponible

en précisant un peu qui vous êtes et les raisons pour lesquelles ce cours vous intéresse.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Eric Thirion. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique Lazarus : Alcatîz -

Developpez.com

Nous contacter

Participez

Informations légales

Services

Forum Lazarus

Blogs

Hébergement

Partenaires

PlanetHoster
Ikoula

Copyright © 2000-2014 - www.developpez.com


Les bases de la programmation

  • Forums
  • Tutoriels
  • Magazine
  • FAQs
  • Blogs
  • Projets
  • Chat
  • Newsletter
  • Études
  • Emploi
  • Club
  • Contacts
  • Accueil Actualités IT Pro
  • ALM Cycle de vie du logiciel
    • ALM
    • Merise
    • UML
  • Java Plateforme et langage Java
    • Java
    • Java Web
    • Spring
    • Android
    • Eclipse
    • NetBeans
  • .NET Microsoft Framework .NET
    • Microsoft DotNET
    • Visual Studio
    • ASP.NET
    • C#
    • VB.NET
    • Windows Phone
    • Microsoft Azure
  • Dév. Web Développement Web et Webmarketing
    • Développement Web
    • AJAX
    • Apache
    • ASP
    • CSS
    • Dart
    • Flash / Flex
    • JavaScript
    • PHP
    • Ruby & Rails
    • Web sémantique
    • Webmarketing
    • (X)HTML
  • EDI Environnements de Développement Intégré
    • EDI
    • 4D
    • Delphi
    • Eclipse
    • LabVIEW
    • NetBeans
    • MATLAB
    • Scilab
    • Visual Studio
    • WinDev
    • Visual Basic 6
    • Lazarus
    • Qt Creator
  • Programmation Programmation et langages de programmation applicatifs
    • Programmation
    • Débuter - Algorithmique
    • 2D - 3D - Jeux
    • Assembleur
    • C
    • C++
    • Go
    • Objective C
    • Pascal
    • Perl
    • Python
    • Qt
    • XML
    • Autres
  • SGBD Systèmes de Gestion de Bases de Données
    • SGBD & SQL
    • 4D
    • Access
    • Big Data
    • DB2
    • Firebird
    • InterBase
    • MySQL
    • NoSQL
    • PostgreSQL
    • Oracle
    • Sybase
    • SQL-Server
  • Office Bureautique pour l'entreprise
    • Microsoft Office
    • Access
    • Excel
    • Word
    • Outlook
    • PowerPoint
    • SharePoint
    • Microsoft Project
  • Solutions d'entreprise Autres logiciels pour l'entreprise
    • Solutions d'entreprise
    • Big Data
    • Business Intelligence
    • ERP / PGI
    • CRM
    • SAS
    • Cloud Computing
    • SAP
    • Microsoft BizTalk Server
    • Talend
  • Applications Applications logicielles
    • Applications
    • Libres & Open Source
    • OpenOffice & LibreOffice
    • Projets
  • Mobiles Logiciels et matériels mobiles
    • Mobiles
    • Android
    • iOS
    • Windows Phone
  • Systèmes Logiciels et matériels systèmes
    • Systèmes
    • Windows
    • Linux Professionnel
    • Sécurité
    • PC
    • Mac
    • Réseau
    • Green IT
    • Virtualisation
    • Systèmes Embarqués
  • EDI
  • 4D
  • Delphi
  • Eclipse
  • LabVIEW
  • NetBeans
  • MATLAB
  • Scilab
  • Visual Studio
  • WinDev
  • Visual Basic 6
  • Lazarus
  • Qt Creator
  • Actualités Lazarus
  • Forum Lazarus
  • Tutoriels Lazarus
  • Tutoriels Pascal
  • F.A.Q. Lazarus
  • F.A.Q. Pascal
  • Sources Lazarus
  • Livres Lazarus

Les bases de la programmation

Table des matières Plier Déplier

  • Structures de contrôle
    • But de ce chapitre
    • Les expressions logiques
      • Variable booléenne
      • Opérateurs logiques
        • La négation
        • La conjonction
        • La disjonction
      • Expressions logiques
        • Les variables booléennes
        • Utilisation des opérateurs logiques
        • Utilisation des opérateurs de comparaison
          • Les six opérateurs
          • Comparaison d'expressions numériques
          • Comparaison de chaînes de caractères
      • Exercices
    • La conditionnelle
      • Le If
        • Le If … Then
        • Le If … Then … Else
        • If imbriqués
        • L'instruction Exit
      • Case
      • Exercices
    • Les boucles
      • Les boucles While
        • Vocabulaire associé aux boucles
        • Déroulement de la boucle
      • Les boucles Repeat
      • Les boucles For
      • Exercices
    • Corrigés des exercices

Avec ce que nous avons vu dans les deux premiers cours (Premières notions et Sous-programmes), un programme se présente comme un ensemble de sous-programmes qui ne contiennent que deux types d'instructions : des affectations ou des appels de procédures.

Les programmes de ce type ont deux limitations importantes, qui disparaîtront avec les nouvelles notions introduites dans ce cours.

Premièrement, ils ne peuvent pas s'adapter aux données qu'ils traitent. Ils ne pourront donc jamais traiter différents cas de figure ni se protéger contre l'introduction de données erronées. Cette limitation disparaîtra avec l'introduction des conditionnelles.

La deuxième limitation concerne les traitements itératifs dont nous avons déjà eu un aperçu dans le premier cours. Mais il s'agissait alors de traitements itératifs réalisés manuellement par l'utilisateur. Pour répéter un traitement, il reclique sur le bouton concerné. En fait, de nombreux problèmes informatiques nécessitent de pouvoir répéter automatiquement des instructions. Or nous n'avons pour l'instant qu'un seul moyen de le faire : dupliquer ces instructions dans le code autant de fois que nécessaire. Cette solution n'est évidemment pas très pratique. Et comment faire alors si le nombre de répétitions dépend des données ?

Cette deuxième limitation disparaîtra avec l'introduction des boucles.

Les conditionnelles et les boucles utilisent toutes les deux un type d'expression que nous n'avons pas encore étudié : les expressions logiques.

Nous allons donc commencer par là.

Les expressions logiques▲

Variable booléenne▲

Le type booléen est un type de variable, tout comme les types entier ou chaîne de caractères.

Une variable de ce type sert à représenter une information qui peut être vraie ou fausse.

Par exemple, dans un programme de gestion de marchandises, on pourrait avoir une variable RuptureDeStock qui nous permettrait de savoir si le stock de marchandises est épuisé.

De manière générale :

Une variable booléenne est une variable qui peut prendre deux valeurs : true (vrai) et false (faux).

Il n'y a donc que deux littéraux de type booléen.

En Pascal, le type booléen se note Boolean et une variable booléenne se déclare de la manière suivante :

Sélectionnez
Var nom de la variable : Boolean;

On pourrait par exemple écrire :

Sélectionnez
  Var RuptureDeStock : Boolean;
  RuptureDeStock := True;

La première instruction déclare une variable booléenne nommée RuptureDeStock. La deuxième lui affecte la valeur True.

Opérateurs logiques▲

Nous avons déjà vu quelques opérateurs : les opérateurs arithmétiques, qui agissent sur des nombres, ainsi que l'opérateur de concaténation, qui agit sur des chaînes de caractères.

De la même manière, il existe des opérateurs logiques. Ceux-ci permettent de calculer une valeur logique (donc vrai ou faux) à partir d'autres valeurs logiques.

Il existe principalement trois opérateurs logiques : la négation , la conjonction et la disjonction.

La négation▲

En logique, la négation permet de représenter le contraire d'une proposition.

Si quelque chose est vrai, alors son contraire est faux et réciproquement.

En Pascal, l'opérateur de négation se note Not.

La table suivante (que l'on appelle table de vérité) résume l'effet de l'opérateur de négation :

X | Not X
True | False
False | True

Par exemple, avec deux variables booléennes ArticleDisponible et RuptureDeStock, on pourrait écrire :

Sélectionnez
ArticleDisponible := Not RuptureDeStock;

Si RuptureDeStock vaut true avant cette affectation, alors ArticleDisponible vaudra false après cette affectation.

Réciproquement, si RuptureDeStock vaut False avant cette affectation, alors ArticleDisponible vaudra true après cette affectation.

La conjonction▲

La conjonction, représentée en Pascal par l'opérateur And, permet d'exprimer le fait que deux choses sont vraies simultanément.

Voilà la table de vérité de l'opérateur And :

X | Y | X And Y
False | False | False
False | True | False
True | False | False
True | True | True

Prenons un exemple.

La variable booléenne AmpouleFonctionne représente le fait qu'une ampoule électrique fonctionne.

Une autre variable booléenne InterrupteurOn est vraie si et seulement si l'interrupteur est en position On.

La variable booléenne LumiereAllumee représente le fait que la lumière est allumée.

Après l'affectation suivante :

Sélectionnez
LumiereAllumee := AmpouleFonctionne And InterrupteurOn;

la variable LumiereAllumee ne vaudra True que si les variables AmpouleFonctionne et InterrupteurOn valaient également True juste avant l'affectation.

La disjonction▲

La disjonction de deux propositions est vraie si et seulement si au moins une de ces deux propositions est vraie.

Autrement dit, la disjonction de deux propositions n'est fausse que lorsque ces deux propositions sont fausses.

Par exemple, la proposition « Il est bête ou il ne comprend pas » ne sera fausse que si l'individu en question n'est pas bête et a très bien compris.

Attention ! Le « ou logique » n'est pas un « ou exclusif ». La disjonction de deux propositions est vraie lorsque les deux propositions sont vraies. Par exemple, l'affirmation « Il pleut ou il y a du soleil » est vraie lorsqu'il pleut et qu'il y a du soleil en même temps.

L'opérateur de disjonction est représenté en Pascal par le mot-clé Or. Voici sa table de vérité :

X | Y | X Or Y
False | False | False
False | True | True
True | False | True
True | True | True

Prenons un exemple.

La variable booléenne AmpouleFoutue représente le fait qu'une ampoule électrique ne fonctionne pas.

Une autre variable booléenne InterrupteurOff est vraie si et seulement si l'interrupteur est en position Off.

La variable booléenne LumiereEteinte représente le fait que la lumière est éteinte.

Après l'affectation suivante :

Sélectionnez
LumiereEteinte := AmpouleFoutue Or InterrupteurOff;

la variable LumiereEteinte ne vaudra True que si la variable AmpouleFoutue et/ou la variable InterrupteurOff avaient la valeur True juste avant l'affectation.

Expressions logiques▲

Une expression logique est une expression de type booléen, c'est-à-dire une expression pouvant prendre la valeur vrai ou faux.

Les variables booléennes▲

Vous connaissez déjà un cas particulièrement simple d'expression logique : ce sont les noms de variables booléennes.

Par exemple, si la variable LumiereEteinte est déclarée comme une variable booléenne, le simple nom de variable LumiereEteinte peut être considéré comme une expression logique.

Utilisation des opérateurs logiques▲

On peut également construire des expressions logiques en combinant d'autres expressions logiques avec les opérateurs Not, And, Or.

Supposons, par exemple, que x, y et z soient des variables booléennes. Alors Not x, x And y, x Or y sont des expressions logiques. Tout comme z Or (x And y), (Not z And x) et (x Or y)…

De manière générale :

  • Si E est une expression logique, alors Not E est une expression logique.
  • Si E et F sont des expressions logiques, alors E And F est une expression logique.
  • Si E et F sont des expressions logiques, alors E Or F est une expression logique.
  • Si E est une expression logique, alors (E) est une expression logique.
Utilisation des opérateurs de comparaison▲

Il existe encore un autre moyen de construire une expression logique : en utilisant des opérateurs de comparaison.

Un opérateur de comparaison est un opérateur qui permet de comparer deux expressions de même type.

Les six opérateurs▲

Il existe six opérateurs de comparaison :

Symboles mathématiques | Signification
= | Égal à
≠≠ | Différent de
< | Strictement inférieur à
> | Strictement supérieur à
≤ | Inférieur ou égal à
≥ | Supérieur ou égal à

La notation des opérateurs de comparaison varie selon les langages :

Symboles mathématiques | Pascal | Langage C
= | = | ==
≠ | <> | !=
< | < | <
> | > | >
≤ | <= | <=
≥ | >= | >=
Comparaison d'expressions numériques▲

L'utilisation la plus fréquente des opérateurs de comparaison est la comparaison d'expressions numériques.

La plus simple est la comparaison directe de deux variables numériques. Exemple :

x | y | x = y | x <> y | x < y | x >= y | x > y | x <= y
1 | 1 | True | False | False | True | False | True
1 | 2 | False | True | True | False | False | True
2 | 1 | False | True | False | True | True | False

Mais on peut bien sûr comparer des expressions numériques quelconques. Par exemple, pour x = 1 et y = 2, on aurait :

Expression logique | Valeur
(x+1) = y | True
(x+1)*(y+1) = 6 | True
x+1 <= 2 | True
x-1 < -y | False
Comparaison de chaînes de caractères▲

En Pascal, les six opérateurs de comparaison peuvent être utilisés avec des chaînes de caractères, en se basant sur l'ordre alphabétique. Exemple :

x | y | x = y | x <> y | x < y | x >= y | x > y | x <= y
'Trac' | 'Trac' | True | False | False | True | False | True
'Trac' | 'Truc' | False | True | True | False | False | True
'Truc' | 'Trac' | False | True | False | True | True | False

Exercices▲

  • Énoncés ;
  • Pour obtenir les corrigés, rendez-vous au bas de la page.

La conditionnelle▲

La programmation n'aurait que très peu d'intérêt si elle ne permettait pas aux ordinateurs de réagir différemment selon différentes conditions.

Une machine programmable doit pouvoir effectuer des traitements différents selon les données qu'elle doit traiter.

Cela est possible grâce à l'existence des structures de contrôle dans les langages de programmation : la conditionnelle et les boucles.

L'objet de ce chapitre est d'expliquer le fonctionnement d'une conditionnelle.

Le If▲

La conditionnelle If existe en deux variantes : le If ... Then et le If … Then … Else.

Le If … Then▲

La structure de contrôle If … Then s'écrit de la manière suivante :

Sélectionnez
If condition Then
Begin
    Instructions à exécuter
    si la condition est vraie
End;

La condition n'est rien d'autre qu'une expression logique.

Les instructions entre begin et end ne seront exécutées que si la condition est vraie.

S'il n'y a qu'une seule instruction à exécuter, on peut omettre les mots-clés begin et end :

Sélectionnez
If condition Then Instruction à exécuter;

Prenons un exemple :

Sélectionnez
If n > 0 Then n := n - 1;

Si, par exemple, n vaut 5 juste avant l'exécution du If … Then, la condition n > 0 est vérifiée. L'ordinateur va donc exécuter l'affectation n := n - 1. n vaudra donc 4 à la sortie de la conditionnelle.

Si n vaut -1, par contre, la condition n > 0 n'est pas vérifiée. L'affectation ne sera pas exécutée et n vaudra toujours -1 à la sortie de la conditionnelle.

Le If … Then … Else▲

Le If … Then … Else est une autre forme de conditionnelle.

Elle permet d'exécuter certaines instructions si une condition est vraie, et d'autres si elle est fausse.

Voilà sa syntaxe :

Sélectionnez
If condition Then
begin
    Instructions à exécuter
    si la condition est vraie
End
Else
begin
   Instructions à exécuter
   si la condition est fausse
End;

De même, les begin et end ne sont pas nécessaires s'il n'y a qu'une seule instruction.

Prenons un exemple :

Sélectionnez
If n > 0 Then
   n := n - 1
Else
   n := n + 1;

Remarquez que nous n'avons pas mis de point-virgule après la première affectation. Si nous l'avions fait, le compilateur aurait généré une erreur de syntaxe, car il aurait interprété ceci comme un if … then simple suivi du mot-clé else, qui n'aurait rien à voir ici dans ce cas.

Par contre, le point-virgule est nécessaire après la deuxième affectation pour marquer la fin de la conditionnelle.

Que se passe-t-il lors de l'exécution de cette conditionnelle ?

Supposons, par exemple, que n vaut 5 juste avant l'exécution du If … Then … Else. La condition n > 0 est vérifiée. L'ordinateur va donc exécuter l'affectation n := n - 1. Par contre, l'affectation n := n + 1 ne sera pas exécutée. n vaudra donc 4 à la sortie de la conditionnelle.

Si n vaut -1, par contre, la condition n > 0 n'est pas vérifiée. L'affectation n := n - 1 n'est donc pas exécutée. Mais comme la condition est fausse, l'affectation n := n + 1 sera exécutée. n vaudra donc 0 à la sortie de la conditionnelle.

If imbriqués▲

Les instructions à l'intérieur d'un If ne sont pas forcément des affectations comme dans les exemples précédents.

On peut également y mettre d'autres structures de contrôle et, en particulier, d'autres If.

Exemple :

Sélectionnez
If n > 0 Then
    n := n - 1
Else
    If n >= -3 Then
         n := n + 1
    Else
         n := 2 * n;

Pour n = 5, l'instruction exécutée est n := n - 1. n vaudra donc 4 à la sortie de la conditionnelle.

Pour n = -1, l'instruction exécutée est n := n + 1. n vaudra donc 0 à la sortie de la conditionnelle.

Pour n = -4, l'instruction exécutée est n := 2 * n. n vaudra donc -8 à la sortie de la conditionnelle.

Dans l'exemple précédent, on a deux niveaux d'imbrication. Mais on peut avoir autant de niveaux d'imbrication qu'on le souhaite.

L'instruction Exit▲

L'imbrication de If peut conduire à des programmes plus rapides (voir l'exercice Min-Max-Med), mais elle peut également compliquer l'écriture du code, notamment dans la protection d'un programme contre les erreurs.

Prenons par exemple un programme utilisant une date définie par un jour (nombre entier entre 1 et 31), un mois (nombre entier entre 1 et 12) et une année de ce siècle (nombre entier supérieur à 2000). Ces trois données sont lues depuis trois zones de texte (respectivement ZT_Jour, ZT_Mois et ZT_Annee) dans des variables entières, respectivement Jour, Mois, Annee. Ce traitement est effectué par la procédure évènementielle associée au bouton BT_GererDate.

Pour tester la validité de ces données avec des If imbriqués, on écrirait le code suivant :

Sélectionnez
procedure TForm1.BT_GererDateClick(Sender: TObject)
var Jour,Mois,Annee: integer;
begin
 LireEntier (Jour, ZT_Jour);
 If ( Jour < 1) or (Jour > 31) Then
   ShowMessage ('Numéro du jour non valide!')
 Else
 begin
   LireEntier (Mois, ZT_Mois);
   If (Mois < 1) or (Mois > 12) Then
     ShowMessage('Numéro du mois non valide!')
   Else
   Begin
     LireEntier(Annee,ZT_Annee);
     If Annee < 2000 Then
       ShowMessage ('Année non valide!')
     Else
     Begin
       Traitement de la date
     End
   End
 End
End;

Pour raccourcir ce genre d'écriture, on peut utiliser l'instruction Exit, qui permet de forcer la fin de l'exécution du sous-programme qui la contient. Avec cette instruction, l'exemple précédent peut être écrit sans aucune imbrication de If :

Sélectionnez
procedure TForm1.BT_GererDateClick(Sender: TObject)
var Jour,Mois,Annee: integer;
begin
 LireEntier (Jour, ZT_Jour);
 If ( Jour < 1) or (Jour > 31) Then
 Begin ShowMessage ('Numéro du jour non valide!'); Exit End;

 LireEntier (Mois, ZT_Mois);
 If (Mois < 1) or (Mois > 12) Then
 Begin ShowMessage('Numéro du mois non valide!'); Exit End;

 LireEntier(Annee,ZT_Annee);
 If Annee < 2000 Then
 Begin ShowMessage ('Année non valide!'); Exit End;

  Traitement de la date

End;

Case▲

La conditionnelle Case est utilisée lorsque l'on veut effectuer différents traitements selon la valeur d'une expression quelconque.

Voici sa syntaxe :

Sélectionnez
Case Expression Of
       V1 :
         Instructions à exécuter lorsque
         l'expression vaut V1 ;
       V2 :
         Instructions à exécuter lorsque
         l'expression vaut V2 ;
       .
       .
       VN :
         Instructions à exécuter lorsque
         l'expression vaut VN ;
Else
    Instructions à exécuter dans les autres cas ;
End;

La partie Else est optionnelle.

Voici, par exemple, comment déterminer le nom d'un mois en fonction de son numéro avec une instruction Case :

Sélectionnez
Case NumeroMois  Of
     1 : NomMois :='janvier';
     2 : NomMois :='février';
     .
     .
     12 : NomMois :='décembre';
Else
 ShowMessage ('Numéro de mois inexistant!')
End;

Exercices▲

  • Exercices avec ordinateur ;
  • Exercices sans ordinateur ;
  • Pour obtenir les corrigés, rendez-vous au bas de la page.

Les boucles▲

Tout comme les conditionnelles, les boucles sont des structures de contrôle fondamentales de la programmation.

Par l'intermédiaire d'une boucle, on peut demander à un ordinateur de répéter une partie du code.

Nous allons présenter trois types de boucles : les boucles While, les boucles Repeat et les boucles For.

En fait, la boucle While suffirait à écrire n'importe quel programme. Mais dans certains cas, l'écriture d'une boucle avec Repeat ou avec For est plus pratique.

Les boucles While▲

Une boucle While permet de répéter du code tant qu'une certaine condition est vraie.

Elle s'écrit de la manière suivante :

Sélectionnez
While condition do
begin
 Instructions à répéter
 tant que la condition est vraie
End;

La condition est une expression logique quelconque.

Voilà un exemple précis de boucle While :

Sélectionnez
S := 0;
i := 1;
While i <= n Do
begin
     S := S + i;
     i := i + 1;
End;

Cette boucle permet de calculer la somme S des n premiers entiers : 1 + 2 + 3 + … + n. Pour n = 4, on aura par exemple S = 1 + 2 + 3 + 4 = 10 à la fin de l'exécution de ce code.

Vocabulaire associé aux boucles▲

Un peu de vocabulaire :

Chaque exécution du code à l'intérieur d'une boucle s'appelle une itération.

Dans notre exemple, une itération est une exécution des instructions S := S + i; et i := i + 1;.

Généralement, une boucle est précédée d'un certain nombre d'affectations que l'on appelle des initialisations. Elles servent à donner des valeurs initiales aux variables de la boucle (c'est-à-dire toutes celles qui vont varier dans la boucle).

Dans notre exemple, ce sont les instructions S := 0 et i := 1.

Dans une boucle, on trouve fréquemment une variable qui joue le rôle de compteur (car elle compte en quelque sorte le nombre d'itérations). La valeur de cette variable est augmentée de 1 à chaque itération par une instruction de la forme variable := variable + 1, que l'on appelle une incrémentation.

Dans notre exemple, le compteur est représenté par la variable i. À chaque itération, la valeur de i est incrémentée par l'affectation i := i + 1.

Déroulement de la boucle▲

Nous allons à présent dérouler la boucle donnée en exemple, c'est-à-dire détailler toutes les instructions qui seront exécutées jusqu'à ce que la boucle s'arrête.

Supposons que n vaut 3 avant d'entrer dans la boucle. Voilà exactement ce qui se passe lorsque cette boucle est exécutée :

Instruction exécutée | Valeur de i | Valeur de S |
S := 0 | 0 | 0 | Initialisation
i := 1 | 1 | 0
S := S + i | 1 | 1 | 1re itération
i := i + 1 | 2 | 1
S := S + i | 2 | 3 | 2e itération
i := i + 1 | 3 | 3
S := S + i | 3 | 6 | 3e itération
i := i + 1 | 4 | 6

Dans une boucle While, la condition d'arrêt est testée avant l'exécution d'une itération.

Donc avant d'entrer la première fois dans la boucle, l'ordinateur va tester la condition i ≤ n. Comme i vaut 1 et n vaut 3, il va rentrer dans la boucle et faire une première itération.

À la fin de la première itération, i vaut 2. La condition i ≤ n est toujours vérifiée. L'ordinateur va donc faire une deuxième itération.

À la fin de la deuxième itération, i vaut 3. La condition i ≤ n est toujours vérifiée. L'ordinateur va donc faire une troisième itération.

À la fin de la troisième itération, i vaut 4. Ce sera donc la dernière itération, car à présent la condition i ≤ n est fausse.

Les boucles Repeat▲

La boucle Repeat s'écrit de la manière suivante :

Sélectionnez
Repeat
   Instructions à répéter
   jusqu'à ce que la condition soit vraie
Until condition

Remarques :

  • avec une boucle Repeat, le code à répéter est toujours exécuté au moins une fois ;
  • il n'y a pas de Begin … End englobant les instructions à répéter, car Repeat et Until jouent déjà ce rôle.

Voici par exemple comment calculer la somme des n premiers nombres entiers avec une boucle Repeat :

Sélectionnez
S := 0;
i := 0;
Repeat
  i := i + 1;
  S := S+ i
Until i = n;

Comme précédemment, supposons que n vaut 3 et voyons ce qui se passe exactement lorsque la boucle Repeat est exécutée :

Instruction exécutée | Valeur de i | Valeur de S |
S := 0 | 0 | 0 | Avant d'entrer dans la boucle
i := 0 | 0 | 0
i := i + 1 | 1 | 0 | 1re itération
S := S + i | 1 | 1
i := i + 1 | 2 | 1 | 2e itération
S := S + i | 2 | 3
i := i + 1 | 3 | 3 | 3e itération
S := S + i | 3 | 6

Avant d'entrer dans la boucle, S et i sont initialisés à 0.

À la fin de chaque itération, l'ordinateur va tester si i est égal à n (donc à 3 dans notre exemple).

Cette condition n'est réalisée qu'à la fin de la troisième itération.

Les boucles For▲

Fondamentalement, une boucle For n'est rien d'autre qu'un raccourci d'écriture pour un certain type de boucle While que l'on rencontre très fréquemment :

Sélectionnez
compteur := valeur initiale;
While compteur <= valeur finale Do
Begin
   Instructions à répéter
   compteur := compteur + 1;
End;

Avant la boucle, on affecte une valeur initiale au compteur.

À chaque itération, le compteur est incrémenté.

La boucle s'arrête dès que le compteur dépasse la valeur finale.

La boucle For permet d'écrire ce type de boucle de manière plus concise, comme suit :

Sélectionnez
For compteur :=  valeur initiale To valeur finale Do
Begin
   Instructions à répéter
End;

On économise ainsi l'écriture de l'incrémentation du compteur à l'intérieur de la boucle.

Attention : l'incrémentation du compteur n'est pas écrite, mais elle est bien exécutée.

Voici, par exemple, la somme des n premiers nombres entiers avec une boucle For et avec une boucle While :

En boucle For | En boucle While
S := 0; For i := 1 To n Do Begin S := S+i End | S := 0; i := 1; While i <= n Do Begin S := S+i; i := i+1 End

Exercices▲

  • Énoncés ;
  • Pour obtenir les corrigés, voyez ci-dessous.

Corrigés des exercices▲

Pour obtenir les corrigés, le téléchargement n'est possible que via un login et un mot de passe, que vous pouvez obtenir en envoyant un mail à l'adresse suivante :

Image non disponible

en précisant un peu qui vous êtes et les raisons pour lesquelles ce cours vous intéresse.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Eric Thirion. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique Lazarus : Alcatîz -

Developpez.com

Nous contacter

Participez

Informations légales

Services

Forum Lazarus

Blogs

Hébergement

Partenaires

PlanetHoster
Ikoula

Copyright © 2000-2014 - www.developpez.com


Les bases de la programmation

  • Forums
  • Tutoriels
  • Magazine
  • FAQs
  • Blogs
  • Projets
  • Chat
  • Newsletter
  • Études
  • Emploi
  • Club
  • Contacts
  • Accueil Actualités IT Pro
  • ALM Cycle de vie du logiciel
    • ALM
    • Merise
    • UML
  • Java Plateforme et langage Java
    • Java
    • Java Web
    • Spring
    • Android
    • Eclipse
    • NetBeans
  • .NET Microsoft Framework .NET
    • Microsoft DotNET
    • Visual Studio
    • ASP.NET
    • C#
    • VB.NET
    • Windows Phone
    • Microsoft Azure
  • Dév. Web Développement Web et Webmarketing
    • Développement Web
    • AJAX
    • Apache
    • ASP
    • CSS
    • Dart
    • Flash / Flex
    • JavaScript
    • PHP
    • Ruby & Rails
    • Web sémantique
    • Webmarketing
    • (X)HTML
  • EDI Environnements de Développement Intégré
    • EDI
    • 4D
    • Delphi
    • Eclipse
    • LabVIEW
    • NetBeans
    • MATLAB
    • Scilab
    • Visual Studio
    • WinDev
    • Visual Basic 6
    • Lazarus
    • Qt Creator
  • Programmation Programmation et langages de programmation applicatifs
    • Programmation
    • Débuter - Algorithmique
    • 2D - 3D - Jeux
    • Assembleur
    • C
    • C++
    • Go
    • Objective C
    • Pascal
    • Perl
    • Python
    • Qt
    • XML
    • Autres
  • SGBD Systèmes de Gestion de Bases de Données
    • SGBD & SQL
    • 4D
    • Access
    • Big Data
    • DB2
    • Firebird
    • InterBase
    • MySQL
    • NoSQL
    • PostgreSQL
    • Oracle
    • Sybase
    • SQL-Server
  • Office Bureautique pour l'entreprise
    • Microsoft Office
    • Access
    • Excel
    • Word
    • Outlook
    • PowerPoint
    • SharePoint
    • Microsoft Project
  • Solutions d'entreprise Autres logiciels pour l'entreprise
    • Solutions d'entreprise
    • Big Data
    • Business Intelligence
    • ERP / PGI
    • CRM
    • SAS
    • Cloud Computing
    • SAP
    • Microsoft BizTalk Server
    • Talend
  • Applications Applications logicielles
    • Applications
    • Libres & Open Source
    • OpenOffice & LibreOffice
    • Projets
  • Mobiles Logiciels et matériels mobiles
    • Mobiles
    • Android
    • iOS
    • Windows Phone
  • Systèmes Logiciels et matériels systèmes
    • Systèmes
    • Windows
    • Linux Professionnel
    • Sécurité
    • PC
    • Mac
    • Réseau
    • Green IT
    • Virtualisation
    • Systèmes Embarqués
  • EDI
  • 4D
  • Delphi
  • Eclipse
  • LabVIEW
  • NetBeans
  • MATLAB
  • Scilab
  • Visual Studio
  • WinDev
  • Visual Basic 6
  • Lazarus
  • Qt Creator
  • Actualités Lazarus
  • Forum Lazarus
  • Tutoriels Lazarus
  • Tutoriels Pascal
  • F.A.Q. Lazarus
  • F.A.Q. Pascal
  • Sources Lazarus
  • Livres Lazarus

Les bases de la programmation

Table des matières Plier Déplier

  • Les tableaux
    • But de ce chapitre
    • Les tableaux à une dimension
      • Exemple introductif
      • Le cas général
      • Utilisation de constantes pour définir la dimension d'un tableau
    • Tableaux remplis partiellement
      • Représentation
      • Adjonction d'une valeur
        • Adjonction à la fin
        • Insertion
      • Suppression d'un élément
        • Suppression du dernier élément
        • Suppression d'un élément quelconque (différent du dernier)
      • Notion de pile
    • Les tableaux à deux dimensions
      • Déclaration en Pascal
      • Traitement de tous les éléments
      • Traitement d'une ligne
      • Traitement d'une colonne
    • Exercices

pitre▲

Jusque là, nous n'avons que très peu de moyens de représenter les données d'un programme. Une donnée en mémoire est soit une variable de type Integer, soit une variable de type Double, String ou Boolean. Pour chaque donnée du programme, nous avons donc ainsi une unique variable.

En réalité, comme nous allons le voir dans ce cours et le suivant (cours concernant les types structurés), une même variable peut contenir un ensemble de données. En particulier, lorsque toutes ces données sont de même type, il est possible de les stocker dans une variable de type tableau.

L'objectif de ce cours est de vous apprendre à définir et à utiliser cette nouvelle manière de représenter les données.

Les tableaux à une dimension▲

Exemple introductif▲

Supposons qu'on ait le problème suivant : on voudrait écrire une procédure permettant de multiplier dix nombres entiers par 3.

Les dix entiers sont des variables globales. Il faut donc tout d'abord déclarer dix variables globales :

Sélectionnez
Var N1, N2, ..., N10 : Integer;

Et la procédure de multiplication devrait comporter dix instructions :

Sélectionnez
Procedure MultiplierPar3 ();
Begin
 N1 := N1 * 3;
 N2 := N2 * 3;
 .
 .
 N10 := N10 * 3;
End;

Pour dix entiers, cette manière de procéder est envisageable. Mais imaginez qu'on ait le même problème avec un million d'entiers !

Ne croyez pas, d'ailleurs, que le traitement d'un million d'entiers soit un problème totalement irréaliste, car les ordinateurs ont souvent à traiter des millions de nombres en même temps. En informatique, ce type de problème est tout à fait courant.

Malheureusement, on ne peut pas utiliser de boucle pour faire ceci, car on n'a aucun moyen de faire varier le nom d'une variable !

On aurait envie d'écrire quelque chose du genre :

Sélectionnez
Var i : Integer;
For i:=1 To 10 Do Var Ni : Integer;

Procedure MultiplierPar3 ();
Var i : Integer;
Begin
  For i := 1 To 10 Do Ni := Ni * 3;
End;

en pensant que Ni serait successivement remplacé par N1, N2, … , N10.

Malheureusement, ce mécanisme de génération automatique de nom de variable n'est pas possible !

Alors, comment faire ?

La solution est d'utiliser un tableau.

Pour utiliser un tableau, il faut tout d'abord déclarer une variable de type tableau.

Avec l'exemple précédent, ce serait :

Sélectionnez
Var N : array [1 .. 10] Of Integer;

Cette écriture revient à déclarer dix entiers N1, N2, … , N10, mais elle est beaucoup plus concise. Lorsqu'un interpréteur ou un compilateur rencontre une déclaration de tableau dans un programme, il alloue de la mémoire pour l'ensemble du tableau. Dans notre exemple, il y aurait donc une allocation mémoire de 20 octets en supposant des entiers 16 bits :

Image non disponible

D'autre part, il devient à présent possible de multiplier les dix entiers du tableau par 3 avec une boucle For, comme ceci :

Image non disponible

Sélectionnez
Procedure MultiplierPar3 ();
Var i : Integer;
Begin
For i := 1 To 10 Do N[i] := N[i] * 3;
End;

L'écriture N[i] désigne le ième élément du tableau, autrement dit l'élément d'indice i du tableau N.

Vous constaterez qu'avec un million d'entiers, le code du programme n'est pas plus long :

Sélectionnez
Var N : array [1 .. 1000000] of Integer;

Procedure MultiplierPar3 ();
Var i : Integer;
Begin
For i := 1 To 1000000 Do  N[i] := N[i] * 3;
End;

Le cas général▲

En Pascal, la déclaration d'un tableau s'écrit :

Sélectionnez
Var nom du tableau
: array [indice min .. indice max] Of type des éléments;

où indice min et indice max sont deux entiers qui représentent respectivement le plus petit indice et le plus grand indice des éléments du tableau.

Le type des éléments peut varier d'un tableau à l'autre, mais tous les éléments d'un même tableau sont forcément du même type.

Voici, par exemple, la déclaration d'un tableau de chaînes de caractères :

Sélectionnez
Var Prenom : array [1..1000 ] Of String;

On pourra ensuite affecter une valeur à un élément du tableau, tout comme on affecte une valeur à une variable. Par exemple l'instruction :

Sélectionnez
Prenom[3] := 'Jeanne';

affecte la chaîne de caractères 'Jeanne' à l'élément d'indice 3 du tableau Prenom.

Utilisation de constantes pour définir la dimension d'un tableau▲

Supposons que vous ayez à modifier un programme très long (plusieurs milliers de lignes, par exemple), qui utilise un tableau de T de dix entiers déclaré par :

Sélectionnez
Var T : array [1 .. 10] Of Integer;

On vous demande de changer la dimension du tableau T de 10 à 20.

Supposons que dans ce programme figurent des centaines d'instructions faisant référence à la dimension du tableau T, c'est-à-dire au nombre 10.

Dans toutes ces instructions, il va donc falloir remplacer le nombre 10 par le nombre 20.

Les constantes servent en particulier à éviter ce genre de manipulation longue et fastidieuse.

Supposons à présent que le programme initial ait été écrit en utilisant une constante DIMENSION, qui représente le nombre d'éléments du tableau T, déclarée comme suit :

Sélectionnez
Const DIMENSION = 10;

et que le tableau soit déclaré comme suit :

Sélectionnez
Var T : array [1..DIMENSION] of Integer;

Si le développeur qui a écrit le programme a bien fait les choses, il aura fait référence à la constante DIMENSION (et non pas 10 !) dans toutes les instructions utilisant la dimension du tableau T.

Un programme écrit de cette manière est beaucoup plus facile à mettre à jour. En effet, pour changer la dimension du tableau de 10 à 20, il vous suffit à présent de modifier une seule ligne de programme : la déclaration de la constante DIMENSION. c'est-à-dire que vous allez la remplacer par :

Sélectionnez
Const DIMENSION = 20;

et tout le reste fonctionnera tout seul !

Vous vous demandez peut-être pourquoi on n'aurait pas pu utiliser une variable à la place de la constante. c'est-à-dire déclarer :

Sélectionnez
  Var DIMENSION : Integer;
   T : array [1..DIMENSION] of Integer;

puis faire DIMENSION := 10; au début du programme.

Ceci ne fonctionne pas !

Pourquoi ?

Parce que la dimension d'un tableau ne peut pas être modifiée pendant l'exécution d'un programme.

En effet, un tableau est un objet statique, c'est-à-dire que l'espace mémoire qui lui est alloué ne peut être modifié en cours d'exécution.

Cela présente un certain nombre d'inconvénients. En particulier, si vous ne connaissez pas a priori la taille de tableau qu'il vous faudra pour résoudre un problème particulier, la seule solution que vous avez est de prévoir large, de manière à ce que vous ne risquiez pas d'avoir un dépassement de capacité (taille de tableau insuffisante pour enregistrer toutes les données).

Nous verrons ultérieurement qu'il existe d'autres manières de représenter les informations en mémoire (on parle de structures de données), qui permettent d'éviter ce problème.

Tableaux remplis partiellement▲

Représentation▲

Nous avons vu qu'un tableau est un objet statique, c'est-à-dire que sa dimension ne peut pas être modifiée pendant l'exécution d'un programme.

Il faut donc toujours prévoir large, c'est-à-dire déclarer un tableau de taille suffisante pour tous les cas de figure.

Cela signifie que l'on va, en général, remplir les tableaux partiellement jusqu'à un certain indice définissant la fin des données.

Pour représenter un tableau rempli partiellement, il faut donc nécessairement une variable entière qui contiendra à tout moment l'indice de fin des données.

Pour fixer les idées, prenons une variable T pour le tableau et une variable N pour l'indice de fin. On aura donc les déclarations suivantes :

Sélectionnez
Const M = Nombre maximum d'éléments;
Var N : Integer;
    T : array [1..M] of Type;

Les données utiles sont stockées dans les N premiers éléments du tableau :

Image non disponible

Le tableau est « vide » lorsque N vaut 0 et plein lorsque N est égal à M :

Image non disponible Image non disponible

Adjonction d'une valeur▲

Adjonction à la fin▲

Pour ajouter une valeur V à un tableau rempli partiellement, le plus simple est de l'ajouter à la fin. On appelle cette opération empiler. L'élément qui suit le dernier prend la valeur V et l'indice de fin est incrémenté :

Image non disponible

Sélectionnez
 T [N+1] := V;
 N :=  N+1;
Insertion▲

Insérer une valeur dans un tableau est une opération plus complexe. Supposons que l'on souhaite insérer une valeur V à l'indice P (P ≤ N) du tableau. Il faut d'abord « faire de la place » pour cette nouvelle valeur, c'est-à-dire décaler toutes les valeurs entre les indices P et N, d'un cran vers la droite. La valeur V peut ensuite être affectée à l'élément d'indice P. Puis, comme on a ajouté une valeur, l'indice de fin augmente de 1 :

Image non disponible

Sélectionnez
 For i := N DownTo P Do T[i+1] := T[i];
 T[P] := V;
 N := N+1;

Suppression d'un élément▲

Suppression du dernier élément▲

Le dernier élément d'un tableau est le plus simple à supprimer ; pour cela, il suffit de dépiler, c'est-à-dire de décrémenter l'indice de fin de 1 :

Image non disponible

Sélectionnez
 N := N - 1;
Suppression d'un élément quelconque (différent du dernier)▲

Supprimer l'élément d'indice P (P < N ) signifie décaler toutes les valeurs qui suivent P d'un cran vers la gauche, puis décrémenter l'indice de fin :

Image non disponible

Sélectionnez
 For i := P To N-1  Do T[i] := T[i+1];
 N := N-1;

Notion de pile▲

Un tableau rempli partiellement sur lequel on ne fait que des opérations empiler et dépiler est une structure de données fondamentale de l'informatique que l'on appelle une pile (stack en anglais).

En particulier, la gestion mémoire des variables locales et des paramètres des sous-programmes utilise une pile : les valeurs des variables sont empilées lorsque l'on rentre dans le sous-programme et dépilées à la sortie.

Les tableaux à deux dimensions▲

Image non disponible

Déclaration en Pascal▲

Sélectionnez
var T : array [ 1..3,1..4 ] of integer;

T [ i , j ] est l'élément de T se trouvant à la ligne i et à la colonne j.

Exemple : T [2, 3] := 10;

Image non disponible

Traitement de tous les éléments▲

Principe :

Sélectionnez
for i := 1 to 3 do
  begin
    for j := 1 to 4 do
      begin
        Traitement de l'élément T[ i, j ]
      end
  end

Exemple :

Sélectionnez
for i := 1 to 3 do
  begin
    for j := 1 to 4 do
      begin
        T [ i , j ] := j + 3 * (i - 1) ;
      end
  end

Image non disponible

Traitement d'une ligne▲

Principe : traitement de la ligne i :

Sélectionnez
for j := 1 to 4 do
  begin
    Traitement de l'élément T[ i, j ]
  end

Exemple : traitement de la ligne 2 :

Sélectionnez
for j:= 1 to 4 do
  begin
    T [2, j ] := 2 * j ;
  end

Image non disponible

Traitement d'une colonne▲

Principe : traitement de la colonne j :

Sélectionnez
for i := 1 to 3 do
  begin
    Traitement de l'élément T[ i, j ]
  end

Exemple : traitement de la colonne 3 :

Sélectionnez
for i := 1 to 3 do
  begin
    T [ i, 3 ] := 4 - i ;
  end

Image non disponible

Exercices▲

Retrouvez les énoncés d'exercices sur le site de l'auteur :

  • les tableaux à une dimension :
    • exercices sous Lazarus,
    • exercices sur papier.
  • tableaux remplis partiellement ;
  • les tableaux à deux dimensions.

Pour obtenir les corrigés, le téléchargement n'est possible que via un login et un mot de passe, que vous pouvez obtenir en envoyant un mail à l'adresse suivante :

Image non disponible

en précisant un peu qui vous êtes et les raisons pour lesquelles ce cours vous intéresse.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Eric Thirion. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique Lazarus : Alcatîz -

Developpez.com

Nous contacter

Participez

Informations légales

Services

Forum Lazarus

Blogs

Hébergement

Partenaires

PlanetHoster
Ikoula

Copyright © 2000-2014 - www.developpez.com


Les bases de la programmation

  • Forums
  • Tutoriels
  • Magazine
  • FAQs
  • Blogs
  • Projets
  • Chat
  • Newsletter
  • Études
  • Emploi
  • Club
  • Contacts
  • Accueil Actualités IT Pro
  • ALM Cycle de vie du logiciel
    • ALM
    • Merise
    • UML
  • Java Plateforme et langage Java
    • Java
    • Java Web
    • Spring
    • Android
    • Eclipse
    • NetBeans
  • .NET Microsoft Framework .NET
    • Microsoft DotNET
    • Visual Studio
    • ASP.NET
    • C#
    • VB.NET
    • Windows Phone
    • Microsoft Azure
  • Dév. Web Développement Web et Webmarketing
    • Développement Web
    • AJAX
    • Apache
    • ASP
    • CSS
    • Dart
    • Flash / Flex
    • JavaScript
    • PHP
    • Ruby & Rails
    • Web sémantique
    • Webmarketing
    • (X)HTML
  • EDI Environnements de Développement Intégré
    • EDI
    • 4D
    • Delphi
    • Eclipse
    • LabVIEW
    • NetBeans
    • MATLAB
    • Scilab
    • Visual Studio
    • WinDev
    • Visual Basic 6
    • Lazarus
    • Qt Creator
  • Programmation Programmation et langages de programmation applicatifs
    • Programmation
    • Débuter - Algorithmique
    • 2D - 3D - Jeux
    • Assembleur
    • C
    • C++
    • Go
    • Objective C
    • Pascal
    • Perl
    • Python
    • Qt
    • XML
    • Autres
  • SGBD Systèmes de Gestion de Bases de Données
    • SGBD & SQL
    • 4D
    • Access
    • Big Data
    • DB2
    • Firebird
    • InterBase
    • MySQL
    • NoSQL
    • PostgreSQL
    • Oracle
    • Sybase
    • SQL-Server
  • Office Bureautique pour l'entreprise
    • Microsoft Office
    • Access
    • Excel
    • Word
    • Outlook
    • PowerPoint
    • SharePoint
    • Microsoft Project
  • Solutions d'entreprise Autres logiciels pour l'entreprise
    • Solutions d'entreprise
    • Big Data
    • Business Intelligence
    • ERP / PGI
    • CRM
    • SAS
    • Cloud Computing
    • SAP
    • Microsoft BizTalk Server
    • Talend
  • Applications Applications logicielles
    • Applications
    • Libres & Open Source
    • OpenOffice & LibreOffice
    • Projets
  • Mobiles Logiciels et matériels mobiles
    • Mobiles
    • Android
    • iOS
    • Windows Phone
  • Systèmes Logiciels et matériels systèmes
    • Systèmes
    • Windows
    • Linux Professionnel
    • Sécurité
    • PC
    • Mac
    • Réseau
    • Green IT
    • Virtualisation
    • Systèmes Embarqués
  • EDI
  • 4D
  • Delphi
  • Eclipse
  • LabVIEW
  • NetBeans
  • MATLAB
  • Scilab
  • Visual Studio
  • WinDev
  • Visual Basic 6
  • Lazarus
  • Qt Creator
  • Actualités Lazarus
  • Forum Lazarus
  • Tutoriels Lazarus
  • Tutoriels Pascal
  • F.A.Q. Lazarus
  • F.A.Q. Pascal
  • Sources Lazarus
  • Livres Lazarus

Les bases de la programmation

Table des matières Plier Déplier

  • Les types structurés
    • Introduction
    • Les types structurés
      • Généralités indépendantes du langage de programmation
      • Déclaration
        • Déclaration d'un type structuré
        • Déclaration de variables de type structuré
      • Manipulation des champs d'une structure
      • Définition de types complexes par combinaison
        • Tableaux de structures : les tables
        • Structure avec champs de type structuré
    • Les pointeurs
      • Déclaration d'un pointeur
      • Allocation dynamique de mémoire
    • Listes simplement chaînées
      • Représentation
      • Parcours
      • Adjonction d'un élément
        • En début de liste
        • Insertion après un élément
      • Suppression d'un élément
        • Suppression du premier élément
        • Suppression du suivant
    • Listes doublement chaînées
      • Représentation
      • Adjonction d'un élément
        • En début de liste
        • Insertion après un élément
    • Exercices

Dans le cours sur les tableaux, nous avons vu un premier moyen de mémoriser un ensemble de données dans une variable. Cette manière de procéder est toutefois limitée à des données de même type. Les types structurés que nous allons présenter dans ce cours permettent de lever cette limitation.

Une autre limitation importante des tableaux est qu'ils sont de dimension fixe. On est donc souvent contraint de les surdimensionner, afin de s'assurer qu'ils pourront stocker toutes les données à traiter. Dans ce cours, nous allons voir comment stocker des ensembles de données de taille variable sous formes de listes. Avec cette nouvelle représentation, il sera possible d'utiliser une place mémoire proportionnelle au nombre de données à mémoriser.

Les types structurés▲

Généralités indépendantes du langage de programmation▲

Les concepts manipulés par l'être humain peuvent souvent être décrits par des attributs : un rectangle est caractérisé par sa largeur et sa longueur ; un livre peut être décrit par son titre, son auteur, son année de publication ; une voiture par sa marque, sa couleur, son kilométrage, sa puissance, etc.

Pour pouvoir représenter informatiquement de tels concepts, les langages de programmation permettent de définir des types structurés. Contrairement à un type prédéfini du langage (entier, réel, booléen, etc.), le nom d'un type structuré est défini par le programmeur. Il pourra par exemple définir un type « Livre », puis déclarer ensuite des variables de ce type.

Déclaration▲

Déclaration d'un type structuré▲

Prenons l'exemple du concept « Livre ». Pour représenter informatiquement un livre dans un programme Pascal, on pourra définir un nouveau type de la manière suivante :

Sélectionnez
 Type Livre =
    record
      Titre : String;
      Auteur : String;
      AnneePublication : Integer;
    end ;

On a ainsi défini un nouveau type de variable nommé « Livre ». « Titre », « Auteur » et « AnneePublication » sont les champs de ce type.

Les champs représentent les attributs du concept que l'on veut définir. Chaque champ est défini par son type. Dans notre exemple, Titre et Auteur sont des chaînes de caractères et AnneePublication est un entier.

Déclaration de variables de type structuré▲

À partir du moment où un nouveau type a été défini, il est possible de déclarer des variables de ce type. Une variable de type structuré est appelée une structure ou un enregistrement. Cette déclaration se fait comme pour un type prédéfini.

Par exemple, pour déclarer deux variables l1, l2 de type Livre, on écrira :

Sélectionnez
  Var l1, l2 : Livre;

Manipulation des champs d'une structure▲

Pour accéder aux champs d'une structure, on utilise l'opérateur « . ».

Par exemple, pour affecter la valeur « Visual Basic 6 − Le Guide du programmeur » au titre du livre l1, on écrira :

Sélectionnez
  l1.Titre := 'Visual Basic 6 - Le Guide du programmeur';

Un champ se manipule exactement comme une variable du même type. Par exemple, l1.AnneePublication se manipule exactement comme un entier. Pour obtenir l'âge du livre l1, on peut très bien écrire :

Sélectionnez
  Var Age : Integer;
  Age := AnneeActuelle - l1.AnneePublication;

Définition de types complexes par combinaison▲

Il est possible de définir des types complexes en combinant tableaux et structures, ou bien en combinant les types structurés entre eux. Donnons quelques exemples.

Tableaux de structures : les tables▲

Le type des éléments d'un tableau peut être quelconque. On peut donc définir un tableau dont les éléments sont des types structurés, ou autrement dit un tableau de structures.

Les tableaux de structures sont également appelés tables. Ils jouent un rôle important en informatique, puisqu'on les retrouve dans les bases de données. Nous verrons ultérieurement qu'une base de données n'est rien d'autre qu'un ensemble de tables stockées dans des fichiers.

Pour représenter l'ensemble des livres présents dans une bibliothèque, on peut par exemple utiliser un tableau dont les éléments sont de type Livre :

Sélectionnez
   Var Bibliotheque : array [1..M] of Livre;

La variable Bibliotheque est donc un tableau de structures. La notation

Sélectionnez
   Bibliotheque[6].Auteur

représente alors l'auteur du 6e livre.

Structure avec champs de type structuré▲

Enrichissons un peu notre représentation d'un livre. Pour la gestion d'une bibliothèque, il serait intéressant d'associer une date d'emprunt à chaque livre :

Sélectionnez
   Type Livre =
     record
        Titre : String;
        Auteur : String;
        AnneePublication : Integer;
        DateEmprunt : TDate;
     end;

Le type TDate étant lui-même un type structuré défini par :

Sélectionnez
  Type TDate =
     record
      Jour : Integer;
      Mois : Integer;
      Annee : Integer;
     end;

Supposons que L soit une variable de type Livre. Pour définir la date d'emprunt de L, comme le 27 janvier 2009, on écrira :

Sélectionnez
   L.DateEmprunt.Jour := 27;
   L.DateEmprunt.Mois := 1;
   L.DateEmprunt.Annee := 2009;

Les pointeurs▲

Déclaration d'un pointeur▲

Un pointeur est une variable contenant une adresse.

Rappelons (cf. le chapitre sur les premières notions) qu'une adresse est un entier qui représente la position d'un octet en mémoire. Cet entier permet donc en particulier de repérer la position, ou autrement dit l'adresse, d'une plage mémoire :

Image non disponible

En Pascal, un pointeur se déclare de la manière suivante :

Sélectionnez
 Var NomDuPointeur : ^Type;

où Type représente le type de donnée dont le pointeur contiendra l'adresse.

Exemple :

Sélectionnez
Type Ville =
  record
    Nom : String;
    CodePostal : Integer;
  End;
Var PVille : ^Ville;

Dans cet exemple, la variable PVille est un pointeur sur une donnée de type Ville.

Une autre manière de déclarer un pointeur est de définir tout d'abord un type pointeur de la manière suivante :

Sélectionnez
 Type PointeurSurType = ^Type;

et de déclarer ensuite le pointeur comme une variable de ce type :

Sélectionnez
 Var NomDuPointeur : PointeurSurType;

Avec l'exemple précédent cela donnerait :

Sélectionnez
Type Ville =
  record
    Nom : String;
    CodePostal : Integer;
  End;

PointeurSurVille = ^Ville;

Var PVille : PointeurSurVille;

Allocation dynamique de mémoire▲

Un pointeur permet de créer une variable sans nom, repérée uniquement par l'adresse qu'il contient.

Cette variable est créée par un mécanisme d'allocation dynamique de mémoire, que nous allons décrire à présent.

Les variables générées dynamiquement sont stockées dans une zone spéciale de la mémoire appelée le tas (heap en anglais).

En Pascal, l'allocation dynamique de mémoire se fait grâce à l'opérateur new.

Si p est un pointeur sur un type T, new(p) va allouer de la place mémoire dans le tas pour une variable de ce type et affecter à p l'adresse de cette variable.

Avec notre exemple précédent, new(p) réserverait de la place pour une structure de type Ville et affecterait l'adresse de cette structure à p. Par exemple, si la plage mémoire réservée pour la structure commence à l'adresse 1961, p contiendra cette adresse :

Image non disponible

Une fois que la variable est créée, il est possible d'y accéder par l'opérateur ^. Si p est un pointeur, p^ représente la variable pointée par p.

Avec notre exemple, pour affecter « Strasbourg » au nom de la ville pointé par p, on écrirait :

Sélectionnez
 p^.Nom := 'Strasbourg';

Tout se passe donc comme si p^ était une variable de type Ville.

Listes simplement chaînées▲

Pour l'instant, le seul moyen dont vous disposez pour représenter un ensemble de données est le tableau. Dans un tableau, chaque donnée (de même type) possède un indice définissant sa position dans le tableau.

Les listes sont une autre manière de représenter un ensemble de données n'utilisant pas d'indice et basée sur les pointeurs.

Représentation▲

Une liste simplement chaînée se représente par un ensemble de structures de même type possédant un champ contenant l'adresse de l'élément suivant.

Il serait assez difficile et peu pédagogique de présenter les listes chaînées dans un cas général abstrait. Nous allons donc baser toutes nos explications sur un exemple qui sera facile à généraliser : une liste de structures de type Ville dont voici la déclaration :

Sélectionnez
Type
PVille = ^Ville;

Ville = record
          Nom : String;
          CodePostal : String;
          Suivant : PVille;
        end;

Le champ Suivant sert à mémoriser l'adresse de l'élément suivant de la liste. Dans le cas général, il peut porter n'importe quel nom, mais il doit exister un champ jouant ce rôle.

La liste, quant à elle, est également un pointeur qui contient l'adresse de son premier élément.

Dans notre exemple, on pourrait donc représenter une liste de villes par une variable L de type PVille.

Voici par exemple une liste simplement chaînée contenant quatre villes :

Image non disponible

L contient l'adresse de la première ville (Ribeauvillé). Le champ Suivant de la première ville contient l'adresse de la seconde (Kingersheim), etc.

Le dernier élément de la liste (Nancy) n'a pas d'élément suivant. Son champ Suivant contient la valeur NIL, qui représente en Pascal une adresse inexistante.

Une liste vide se représente simplement par un pointeur de liste égal à NIL (L = NIL dans notre exemple).

Parcours▲

Pour effectuer un traitement sur chaque élément d'une liste, il est nécessaire de savoir parcourir ses éléments. On utilisera pour cela un pointeur, qui prendra successivement la valeur des adresses de chaque élément de la liste. Voici par exemple comment parcourir une liste de villes dont l'adresse est contenue dans le pointeur L :

Sélectionnez
Var p : Pville;

p := L;
While p <> NIL Do
  begin

    { Traitement de l'élément pointé par p }

    p := p^.Suivant;
  end;

Adjonction d'un élément▲

Considérons le problème suivant : on dispose d'une liste de villes L et d'une structure de type Ville pointée par un pointeur p. Comment procéder pour ajouter cette structure dans la liste ?

En début de liste▲

Pour ajouter l'élément p^ au début de la liste L, il suffit de faire :

Sélectionnez
  p^.Suivant := L;
  L := p;

La première affectation « accroche » la structure à la liste et la deuxième met à jour l'adresse de la liste, qui devient celle de p.

Cette méthode fonctionne également avec une liste vide. En effet, dans ce cas L vaut NIL avant l'adjonction. Après l'adjonction, on aura bien une liste d'un élément (p^) avec L = p et p^.Suivant = NIL.

La figure suivante illustre l'adjonction en début de liste de la ville Ribeauvillé :

Image non disponible

Avant l'adjonction :

  • la liste L contient les trois villes Kingersheim, Strasbourg et Nancy ;
  • L contient l'adresse de la structure représentant Kingersheim.

Après l'adjonction :

  • le champ Suivant de la structure représentant Ribeauvillé contient l'adresse de la structure représentant Kingersheim ;
  • L contient l'adresse de la structure représentant Ribeauvillé ;
  • L contient donc à présent les quatre villes Ribeauvillé, Kingersheim, Strasbourg et Nancy.
Insertion après un élément▲

Supposons à présent que la liste n'est pas vide et que l'on souhaite insérer un élément après un certain élément de la liste.

Soit p l'adresse du nouvel élément que l'on souhaite insérer, et Curseur l'adresse de l'élément après lequel on souhaite insérer p^. Les instructions suivantes permettent d'obtenir ce que l'on souhaite :

Sélectionnez
p^.Suivant := Curseur^.Suivant;
Curseur^.Suivant := p;

La figure suivante illustre l'insertion d'une structure représentant Strasbourg, après la structure représentant Kingersheim :

Image non disponible

Suppression d'un élément▲

Suppression du premier élément▲

Pour supprimer le premier élément d'une liste simplement chaînée, il suffit de remplacer l'adresse de la liste par l'adresse de l'élément qui suit le premier.

Avec notre exemple, cela se traduit par l'instruction suivante :

Sélectionnez
 L := L^.Suivant;

Si la liste ne contient qu'un seul élément, ce dernier n'a pas d'élément suivant, mais l'instruction précédente convient également, car, dans ce cas, L^.Suivant contient NIL et la liste devient par conséquent vide.

La figure suivante illustre la suppression du premier élément dans une liste de villes :

Image non disponible

Avant la suppression, L pointe sur Ribeauvillé et la liste contient quatre villes.

Après la suppression, L pointe sur la ville qui suivait Ribeauvillé, c'est-à-dire Kingersheim. De ce fait, elle ne contient plus que les trois villes Kingersheim, Strasbourg et Nancy.

Suppression du suivant▲

Supposons à présent que l'on souhaite supprimer l'élément qui suit un certain élément de la liste d'adresses donnée.

Appelons Curseur l'adresse de cet élément. L'instruction suivante permet de supprimer l'élément qui suit Curseur^ :

Sélectionnez
 Curseur^.Suivant := Curseur^.Suivant^.Suivant;

La figure suivante illustre ce mécanisme. On supprime ici la ville Strasbourg, en faisant pointer la ville précédente (Kingersheim) sur la ville qui suit Strasbourg (Nancy). De ce fait, Strasbourg n'existe plus dans la liste.

Image non disponible

Listes doublement chaînées▲

Dans une liste doublement chaînée, chaque élément est lié non seulement au suivant, mais également au précédent. Cela facilite la suppression d'un élément, puisqu'il faut pour cela avoir l'adresse du précédent. Il devient également plus simple d'insérer un élément avant un élément donné.

Représentation▲

Reprenons notre exemple de liste de villes. Voici comment la représenter avec un chaînage double :

Sélectionnez
Type Ville =
  record
    Nom : String;
    CodePostal : String;
    Suivant : PVille;
    Precedent : Pville;
  end;

PVille = ^Ville;

Voici un exemple de liste doublement chaînée :

Image non disponible

Adjonction d'un élément▲

En début de liste▲

L'adjonction d'un élément en début de liste nécessite deux instructions supplémentaires, puisqu'il faut mémoriser le fait que cet élément n'a pas d'élément précédent et qu'il devient à présent le précédent du premier élément de la liste (avant l'adjonction).

Sélectionnez
  p^.Suivant := L ;
  p^.Precedent := NIL;
  L^.Precedent := p;
  L := p;
Insertion après un élément▲
Sélectionnez
If Curseur^.Suivant <> NIL then
  Curseur^.Suivant^.Precedent := p;
p^.Precedent := Curseur;
p^.Suivant := Curseur^.Suivant;
Curseur^.Suivant := p;

Exercices▲

Téléchargez :

  • les exercices sur les types structurés ;
  • les exercices sur les pointeurs et les listes.

Pour obtenir les corrigés, le téléchargement n'est possible que via un login et un mot de passe, que vous pouvez obtenir en envoyant un mail à l'adresse suivante :

Image non disponible

en précisant un peu qui vous êtes et les raisons pour lesquelles ce cours vous intéresse.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Eric Thirion. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique Lazarus : Alcatîz -

Developpez.com

Nous contacter

Participez

Informations légales

Services

Forum Lazarus

Blogs

Hébergement

Partenaires

PlanetHoster
Ikoula

Copyright © 2000-2014 - www.developpez.com


Les bases de la programmation

  • Forums
  • Tutoriels
  • Magazine
  • FAQs
  • Blogs
  • Projets
  • Chat
  • Newsletter
  • Études
  • Emploi
  • Club
  • Contacts
  • Accueil Actualités IT Pro
  • ALM Cycle de vie du logiciel
    • ALM
    • Merise
    • UML
  • Java Plateforme et langage Java
    • Java
    • Java Web
    • Spring
    • Android
    • Eclipse
    • NetBeans
  • .NET Microsoft Framework .NET
    • Microsoft DotNET
    • Visual Studio
    • ASP.NET
    • C#
    • VB.NET
    • Windows Phone
    • Microsoft Azure
  • Dév. Web Développement Web et Webmarketing
    • Développement Web
    • AJAX
    • Apache
    • ASP
    • CSS
    • Dart
    • Flash / Flex
    • JavaScript
    • PHP
    • Ruby & Rails
    • Web sémantique
    • Webmarketing
    • (X)HTML
  • EDI Environnements de Développement Intégré
    • EDI
    • 4D
    • Delphi
    • Eclipse
    • LabVIEW
    • NetBeans
    • MATLAB
    • Scilab
    • Visual Studio
    • WinDev
    • Visual Basic 6
    • Lazarus
    • Qt Creator
  • Programmation Programmation et langages de programmation applicatifs
    • Programmation
    • Débuter - Algorithmique
    • 2D - 3D - Jeux
    • Assembleur
    • C
    • C++
    • Go
    • Objective C
    • Pascal
    • Perl
    • Python
    • Qt
    • XML
    • Autres
  • SGBD Systèmes de Gestion de Bases de Données
    • SGBD & SQL
    • 4D
    • Access
    • Big Data
    • DB2
    • Firebird
    • InterBase
    • MySQL
    • NoSQL
    • PostgreSQL
    • Oracle
    • Sybase
    • SQL-Server
  • Office Bureautique pour l'entreprise
    • Microsoft Office
    • Access
    • Excel
    • Word
    • Outlook
    • PowerPoint
    • SharePoint
    • Microsoft Project
  • Solutions d'entreprise Autres logiciels pour l'entreprise
    • Solutions d'entreprise
    • Big Data
    • Business Intelligence
    • ERP / PGI
    • CRM
    • SAS
    • Cloud Computing
    • SAP
    • Microsoft BizTalk Server
    • Talend
  • Applications Applications logicielles
    • Applications
    • Libres & Open Source
    • OpenOffice & LibreOffice
    • Projets
  • Mobiles Logiciels et matériels mobiles
    • Mobiles
    • Android
    • iOS
    • Windows Phone
  • Systèmes Logiciels et matériels systèmes
    • Systèmes
    • Windows
    • Linux Professionnel
    • Sécurité
    • PC
    • Mac
    • Réseau
    • Green IT
    • Virtualisation
    • Systèmes Embarqués
  • EDI
  • 4D
  • Delphi
  • Eclipse
  • LabVIEW
  • NetBeans
  • MATLAB
  • Scilab
  • Visual Studio
  • WinDev
  • Visual Basic 6
  • Lazarus
  • Qt Creator
  • Actualités Lazarus
  • Forum Lazarus
  • Tutoriels Lazarus
  • Tutoriels Pascal
  • F.A.Q. Lazarus
  • F.A.Q. Pascal
  • Sources Lazarus
  • Livres Lazarus

Les bases de la programmation

Table des matières Plier Déplier

  • Les fichiers
    • But de ce chapitre
    • Notion de fichier
      • Qu'est-ce qu'un fichier ?
      • Nom de fichier
      • Chemin d'accès
      • Identification d'un fichier par son nom complet
      • Format
      • Les fichiers texte
      • Les fichiers structurés
    • Opérations sur les fichiers
      • Les erreurs d'accès
      • Ouverture et fermeture d'un fichier
        • À quoi sert l'ouverture d'un fichier ?
        • À quoi sert la fermeture d'un fichier ?
      • Accès direct et accès séquentiel
        • Accès direct
        • Accès séquentiel
      • Manipulation des fichiers texte
        • Ouverture d'un fichier texte
        • Opérations sur les lignes
      • Manipulation des fichiers structurés
        • Ouverture d'un fichier structuré
        • Accès séquentiel
        • Accès direct
    • Manipulation de fichiers texte en Pascal
      • Déclaration
      • Assignation
      • Création d'un fichier texte
        • Ouverture en écriture
        • Écriture d'une ligne
      • Lecture d'un fichier texte
        • Ouverture d'un fichier texte en lecture
        • Lecture d'une ligne et test de fin de fichier
    • Les fichier structurés en Pascal
      • Déclaration
      • Assignation
      • Ouverture et fermeture
      • Écriture séquentielle
      • Lecture séquentielle
        • Lecture jusqu'en fin de fichier
    • Exercices

En utilisant ce que nous avons vu dans les cours précédents, vous ne pouvez pas réaliser un programme qui serait capable de conserver ses résultats une fois l'ordinateur éteint. D'autre part, les données doivent être nécessairement fournies par l'utilisateur à chaque exécution du programme. Ces limitations proviennent du type de mémoire utilisé : pour l'instant, vous ne savez traiter que des données stockées en mémoire vive. Or, la mémoire vive est volatile : lorsque l'ordinateur est mis hors tension, tout disparaît !

Ce problème pourrait donc être donc résolu si vous connaissiez les instructions permettant de lire ou d'enregistrer des données dans des mémoires non volatiles, stockées par exemple sur le disque dur ou sur une clé USB, sous la forme de fichiers.

L'objectif de ce cours est de vous décrire précisément les procédures Pascal réalisant ceci.

Notion de fichier▲

Qu'est-ce qu'un fichier ?▲

Un fichier est un ensemble de données informatiques stockées en mémoire auxiliaire (disque dur, CD, DVD, clef USB…) possédant un nom, un chemin d'accès et généralement implicitement associé à un format (format image, format de documents, formats de base de données…).

Le terme de mémoire auxiliaire désigne tout support de stockage de l'information d'un ordinateur autre que la mémoire vive.

Le fait qu'un fichier soit toujours stocké sur mémoire auxiliaire a deux conséquences.

Premièrement, les données contenues dans un fichier ne sont pas perdues lorsque l'ordinateur est mis hors tension (contrairement aux données contenues dans les variables d'un programme !).

Deuxièmement, l'accès à des données stockées dans un fichier est considérablement plus lent qu'un accès à des données contenues en mémoire vive.

Nom de fichier▲

La manière de nommer les fichiers dépend fortement du système d'exploitation. Par exemple, sous Windows, tout fichier possède généralement une extension (.doc, .jpg…) qui permet d'identifier son format.

Chemin d'accès▲

Le chemin d'accès d'un fichier définit sa localisation sur un périphérique donné. C'est une chaîne de caractères qui définit la suite des répertoires à parcourir pour atteindre le fichier depuis la racine du périphérique.

La manière de décrire un chemin dépend du système d'exploitation. Sous Windows, un chemin est une chaîne de caractères de la forme :

Sélectionnez
X:\Répertoire\Répertoire\ ... \Répertoire

où X est une lettre définissant le périphérique utilisé.

Par exemple :

Sélectionnez
C:\Utilisateurs\Thirion\Documents

Identification d'un fichier par son nom complet▲

Le nom d'un fichier ne l'identifie pas de manière unique. Par exemple, il peut exister un fichier nommé CV.doc sous

Sélectionnez
C:\Utilisateurs\Thirion\Documents

et un autre sous

Sélectionnez
C:\Utilisateurs\Einstein\Documents

Pour identifier un fichier de manière unique, il faut utiliser son chemin concaténé avec le nom (simple) du fichier. Par exemple :

Sélectionnez
C:\Utilisateurs\Thirion\Documents\CV.doc

désigne le CV de Thirion, alors que :

Sélectionnez
C:\Utilisateurs\Einstein\Documents\CV.doc

désigne le CV de Einstein.

J'utiliserai par la suite le terme de nom complet, pour désigner le chemin concaténé avec le nom du fichier.

Format▲

Le format d'un fichier est un ensemble de conventions définissant la représentation des données qu'il contient : la localisation et le codage des différents types d'informations contenues dans le fichier.

Le fichier en lui-même ne permet pas à coup sûr de retrouver son format de manière immédiate : comme toute chose en informatique, ce n'est qu'une suite de zéros et de uns. Une manière de vérifier si un fichier est bien dans un format donné est de l'ouvrir avec un logiciel adapté à ce format. Si le logiciel gère correctement le fichier, il est certainement dans le bon format (mais ce n'est pas une certitude !). Une autre manière est de scruter l'en-tête du fichier à la recherche d'une signature numérique propre à son format.

Exemple : si je peux ouvrir un fichier avec Word, il s'agit très certainement d'un fichier respectant un des formats acceptés par Word (.doc, .txt…). Au contraire, si j'essaie d'ouvrir avec Word un fichier qui n'est pas dans un format accepté par Word, il se produira probablement n'importe quoi.

Sous Windows, le format d'un fichier est repérable par l'extension (.doc pour un fichier Word, .exe pour un fichier exécutable…). Mais l'extension d'un fichier peut être changée manuellement… L'extension d'un fichier ne donne donc aucune certitude sur son format !

Les fichiers texte▲

Les fichiers texte sont des fichiers que l'on peut ouvrir avec un éditeur de texte (comme WordPad ou le Bloc-notes) afin de modifier leur contenu.

Ce sont par exemple les fichiers d'extension .txt sous Windows, les fichiers sources d'un programme, les fichiers HTML, etc.).

Un fichier texte est un fichier qui peut être interprété comme une suite de caractères (le plus souvent en code ASCII) affichables à l'écran.

Deux caractères spéciaux servent à coder le passage à la ligne et la fin du fichier.

Les fichiers structurés▲

Les fichiers structurés sont des fichiers interprétables comme une suite d'enregistrements d'un certain type. Tous les enregistrements sont forcément de même taille.

Les fichiers structurés peuvent être vus comme la représentation d'un tableau en mémoire auxiliaire. Rappelez-vous que tous les éléments d'un tableau doivent être de même type. Les éléments du tableau correspondent ici aux enregistrements.

Certains auteurs utilisent le nom de fichiers séquentiels pour désigner des fichiers structurés. Je trouve ce nom très mal adapté car il porte à confusion. Nous verrons pourquoi un peu plus loin.

Opérations sur les fichiers▲

Les langages de programmation permettent de :

  • créer des fichiers ;
  • enregistrer des informations dans un fichier : c'est l'accès en écriture ;
  • lire des informations dans un fichier : c'est l'accès en lecture.

Les erreurs d'accès▲

Le système d'exploitation est responsable de la gestion des fichiers d'un ordinateur. Toute opération sur un fichier se fait donc nécessairement par l'intermédiaire du système d'exploitation. Lorsqu'un programme effectue une opération sur un fichier, il faut que le système d'exploitation autorise cette opération, sans quoi il se produira une erreur d'accès :

Image non disponible

Voilà quelques-unes des erreurs les plus fréquentes :

  • vous faites un accès en lecture sur un fichier qui n'existe pas ;
  • vous faites un accès en lecture sur un fichier, alors que vous n'avez pas de droit de lecture sur ce fichier : ce fichier appartient à un autre utilisateur, c'est un fichier protégé, etc. ;
  • vous faites un accès en écriture sur un fichier, alors que vous n'avez pas de droit d'écriture sur ce fichier ;
  • vous faites un accès en écriture sur un fichier alors qu'un autre programme est en train d'y accéder en lecture ou en écriture ;
  • vous faites un accès en écriture sur un fichier alors que le périphérique qui le contient est plein ;
  • vous faites un accès en lecture ou en écriture « en dehors » du fichier.

Ouverture et fermeture d'un fichier▲

L'accès à un fichier par programmation fonctionne toujours selon le même principe :

  • avant de lire ou d'écrire des informations dans un fichier, il faut l'ouvrir ;
  • lorsque le traitement du fichier est terminé il faut le fermer.
À quoi sert l'ouverture d'un fichier ?▲

Ouvrir un fichier sert (entre autres) à informer le système d'exploitation que l'on a l'intention d'y accéder. C'est à ce moment-là que le système peut refuser l'accès au fichier, ce qui provoquera une erreur d'exécution du programme.

À quoi sert la fermeture d'un fichier ?▲

La fermeture d'un fichier sert (entre autres) à signaler au système d'exploitation que le fichier n'est plus utilisé par le programme, ce qui permettra à un autre programme (éventuellement d'un autre utilisateur) d'y accéder.

Accès direct et accès séquentiel▲

Il y a deux manières d'accéder à un fichier par programmation : l'accès direct et l'accès séquentiel.

Accès direct▲

L'accès direct n'est possible que pour des fichiers définis comme une suite de blocs de taille fixe que l'on appelle enregistrements. Il n'est donc possible que pour les fichiers structurés.

Image non disponible

L'accès direct signifie qu'il est possible d'accéder directement à un enregistrement à partir de sa position dans le fichier : lire le énième enregistrement du fichier ou le modifier. Ce type d'accès est par exemple adapté à la gestion des tables d'une base de données.

Accès séquentiel▲

Dans l'accès séquentiel, une opération de lecture ou d'écriture se fait toujours juste après la dernière partie du fichier lue ou écrite.

Image non disponible

Les fichiers texte n'autorisent que ce type d'accès.

Les fichiers structurés quant à eux autorisent les deux types d'accès (séquentiel ou direct).

Vous comprenez à présent pourquoi le fait d'appeler « fichiers séquentiels » les fichiers structurés est une source de confusion !

Manipulation des fichiers texte▲

Ouverture d'un fichier texte▲

L'ouverture d'un fichier texte se fait en lecture, en écriture ou en ajout. Nous ne traiterons pas ici de l'ouverture en ajout.

Si le fichier est ouvert en lecture, il ne peut y avoir que des lectures jusqu'à la fermeture du fichier.

De même, si le fichier est ouvert en écriture, il ne peut y avoir que des écritures jusqu'à la fermeture du fichier.

Opérations sur les lignes▲

Un fichier texte est normalement organisé en lignes. Les langages de programmation contiennent en général une instruction (ou une procédure) permettant de :

  • lire une ligne du fichier en la recopiant dans une variable de type chaîne de caractères. Cette opération n'est possible que si le fichier est ouvert en lecture. De plus, comme il s'agit d'un accès séquentiel, elle se fait forcément juste après la dernière ligne lue. Sauf si le fichier vient d'être ouvert. Dans ce cas, la ligne lue est nécessairement la première ligne du fichier ;
  • écrire une chaîne de caractères dans une ligne du fichier. De même, cette opération n'est possible que si le fichier est ouvert en écriture et se fait forcément juste après la dernière ligne écrite. Exception pour l'écriture d'une ligne qui suit immédiatement l'ouverture du fichier : ce sera forcément la première ligne du fichier.

Manipulation des fichiers structurés▲

Ouverture d'un fichier structuré▲

Pour ouvrir un fichier structuré, il n'est pas nécessaire de préciser si l'on veut ouvrir le fichier en lecture ou en écriture. En effet, avec un fichier structuré, ces deux types d'opérations (lecture et écriture) peuvent être mélangées dans n'importe quel ordre.

Si le fichier n'existe pas avant l'ouverture, il sera créé.

Accès séquentiel▲

L'accès séquentiel dans un fichier structuré consiste à lire ou à écrire un enregistrement sans préciser sa position. La lecture ou l'écriture se fait nécessairement juste après le dernier enregistrement lu ou écrit. Sauf si le fichier vient juste d'être ouvert. Dans ce cas, l'enregistrement lu ou écrit sera forcément le premier.

Accès direct▲

Dans l'accès direct, on accède à un enregistrement par sa position dans le fichier. Il est par exemple possible de lire le 25e enregistrement, puis de modifier le 122e. On peut parcourir le fichier dans n'importe quel ordre.

Manipulation de fichiers texte en Pascal▲

Déclaration▲

Un fichier texte est représenté en Pascal par le type TextFile. Pour pouvoir manipuler un fichier texte, il faut d'abord déclarer une variable de ce type. Par exemple :

Sélectionnez
var f : TextFile;

Dans toutes les explications qui suivent, f désignera le fichier déclaré de cette manière.

Assignation▲

Avant de pouvoir ouvrir un nouveau fichier texte, il faut d'abord associer la variable fichier au nom du fichier sur le disque. Cela se fait par la procédure AssignFile. Voici, par exemple, comment associer f à un fichier nommé exemple.txt :

Sélectionnez
AssignFile(f,'exemple.txt');

Création d'un fichier texte▲

Ouverture en écriture▲

Après avoir déclaré une variable de type fichier et assigné cette variable à un nom de fichier, on peut l'ouvrir en écriture. Cela a pour effet de créer un nouveau fichier vide. Attention : s'il existe déjà un fichier de même nom sur le disque, le contenu de ce dernier sera perdu !

L'ouverture en écriture, se fait par la procédure rewrite, comme suit :

Sélectionnez
rewrite(f);
Écriture d'une ligne▲

Dès que le fichier est ouvert en écriture, on peut y écrire du texte en utilisant la procédure writeln. Voici, par exemple, comment créer un fichier texte exemple.txt d'une ligne :

Sélectionnez
var f: TextFile;
begin
  assign (f,'exemple.txt');
  rewrite(f);
  writeln (f,'Ceci est un fichier d'une ligne.');
  CloseFile(f);
end;

La procédure CloseFile appelée à la fin sert à fermer le fichier.

Comme on fait de l'accès séquentiel, une écriture se fait forcément après la ligne écrite juste avant. Donc, après l'exécution du code suivant :

Sélectionnez
var f: TextFile;
begin
  assign(f,'exemple.txt');
  rewrite(f);
  writeln(f,'Première ligne du fichier exemple.txt');
  writeln(f,'Deuxième ligne du fichier exemple.txt');
  CloseFile(f);
end;

le fichier exemple.txt contiendra les deux lignes :

Sélectionnez
Première ligne du fichier exemple.txt
Deuxième ligne du fichier exemple.txt

Lecture d'un fichier texte▲

Ouverture d'un fichier texte en lecture▲

Pour ouvrir un fichier en lecture, on utilise la procédure reset de la manière suivante :

Sélectionnez
reset(f);

Comme pour l'ouverture en écriture, il faut que le fichier ait auparavant été assigné à un nom de fichier. Par contre, si ce fichier n'existe pas, cela provoquera une erreur.

Lecture d'une ligne et test de fin de fichier▲

Pour lire une ligne dans le fichier, on utilise la procédure readln, qui affecte la ligne courante à une variable L de type chaîne de caractères :

Sélectionnez
readln(f,L);

Il s'agit ici d'un accès séquentiel, donc une lecture de ligne se fait nécessairement juste après la ligne lue précédemment.

Juste après l'ouverture du fichier, readln transfère la première ligne du fichier dans variable L.

À la deuxième exécution de cette procédure, L contiendra la 2e ligne du fichier et de manière générale, après la énième exécution, L contiendra la énième ligne du fichier.

Bien entendu, on provoquera une erreur d'exécution si l'on essaie de lire au-delà de la fin du fichier. Par exemple, si le fichier exemple.txt ne contient que 10 lignes, la 11e exécution de readln(f,L); provoquera une erreur d'exécution.

Pour éviter cette erreur, il faut utiliser la fonction eof, qui permet de tester si l'on se trouve en fin de fichier. Par exemple, le code suivant permet de lire toutes les lignes du fichier exemple.txt afin d'effectuer un certain traitement sur chaque ligne :

Sélectionnez
Var f : TextFile; L : String;
begin
  assign(f,'exemple.txt');
  reset(f);

  while not eof(f) do
  begin
     readln(f,L);
     // Traitement de la ligne L
  end;
  CloseFile(f);

end;

Les fichier structurés en Pascal▲

Déclaration▲

Un fichier structuré est un fichier constitué d'enregistrements de même type.

Le type d'un enregistrement est a priori n'importe quel type Pascal de taille fixe. En effet, comme chaque enregistrement doit occuper la même taille en mémoire auxiliaire, on ne peut pas déclarer un fichier constitué d'enregistrements de longueur variable. En particulier, le type String sera interdit et devra être remplacé par le type String[n], qui permet de préciser la taille de la chaîne de caractères.

Pour la suite des explications, nous allons utiliser le type structuré Livre déclaré comme suit :

Sélectionnez
Livre =
  record
    Titre : String[100];
    Auteur : String[20];
    AnnéePub : integer;
  end;

Nous pouvons ensuite déclarer un fichier structuré composé d'enregistrements de type Livre comme suit :

Sélectionnez
var f : file of Livre;

Remarquez que le type des enregistrements d'un fichier structuré n'est pas forcément un type structuré. On peut par exemple déclarer un fichier d'entiers (file of integer) ou un fichier de caractères (file of char).

Assignation▲

Tout comme pour les fichiers texte, avant d'ouvrir un fichier structuré il faut associer la variable représentant le fichier à un nom de fichier avec la procédure AssignFile.

Exemple :

Sélectionnez
AssignFile(f,'Bibliotheque.dat');

Cette instruction assigne f au fichier de nom Bibliotheque.dat.

Ouverture et fermeture▲

Pour ouvrir des fichiers structurés, on utilise encore les procédures reset et rewrite. Mais elles n'ont pas la même signification que pour les fichiers texte.

En effet, il sera toujours possible de lire et d'écrire des enregistrements, que le fichier soit ouvert avec reset (ouverture en lecture pour un fichier texte) ou avec rewrite (ouverture en écriture pour un fichier texte).

Pour les fichiers structurés, rewrite n'est pas une ouverture en écriture. Cela signifie seulement que l'on demande au système d'exploitation de créer un nouveau fichier. Avec reset, on demande simplement l'ouverture, sans création d'un nouveau fichier.

Par contre, un fichier ne peut pas être ouvert avec reset s'il n'existe pas. Pour ouvrir un fichier structuré sans savoir auparavant s'il existe ou non, on utilisera la fonction FileExists (fonction permettant de tester l'existence d'un fichier de nom donné) de la manière suivante :

Sélectionnez
 if FileExists('Bibliotheque.dat') then
    reset(f)
 else
    rewrite(f);

Écriture séquentielle▲

L'écriture d'un enregistrement se fait avec la procédure write. Plus précisément, write (f,v) écrit la valeur de la variable v dans le fichier f à la suite du dernier enregistrement lu ou écrit. Bien entendu, la variable doit être de même type que les enregistrements du fichier.

Si le fichier vient d'être ouvert, l'écriture se fait dans le premier enregistrement.

Déclarons par exemple deux structures de type Livre :

Sélectionnez
var VB, Gaston : Livre;

Donnons des valeurs à leurs champs :

Sélectionnez
VB.Titre := 'Visual Basic 6, Le guide du programmeur';
VB.Auteur := 'Frantz';
VB.AnneePub := 2000;
Gaston.Titre := 'Gaston Lagaffe - Des Gaffes et des Dégâts';
Gaston.Auteur := 'Franquin';
Gaston.AnneePub := 1975;

Après les instructions suivantes :

Sélectionnez
AssignFile(f,'Bibliotheque.dat');
rewrite(f);
write(f,Gaston);
write(f,VB);

le fichier Bibliotheque.dat contiendra deux enregistrements de type Livre. Plus précisément, le premier enregistrement contiendra le livre Gaston et le deuxième, le livre VB.

Si, après cela, on fait encore une fois

Sélectionnez
write(f,Gaston);

le fichier contiendra trois enregistrements (Gaston, VB, Gaston).

Si on écrit un enregistrement juste après l'ouverture d'un fichier existant (donc avec un reset), cette écriture se fera dans le premier enregistrement du fichier. Par exemple, après l'exécution du code suivant :

Sélectionnez
AssignFile(f,'Bibliotheque.dat');
reset(f);
write(f, VB);

le fichier contiendra (VB, VB, Gaston).

Lecture séquentielle▲

La lecture d'un enregistrement à la suite du dernier enregistrement lu ou écrit se fait par la procédure read. Plus précisément, pour affecter la valeur d'un enregistrement à une variable v, on écrira read(f,v). Cela suppose bien entendu que la variable est du même type que les enregistrements du fichier.

Si le fichier vient d'être ouvert, l'enregistrement lu sera le premier.

Reprenons la suite de l'exemple précédent. Notre fichier Bibliotheque.dat contient à présent trois enregistrements de type Livre (VB, VB, Gaston).

Pour les récupérer, déclarons trois variables de type Livre :

Sélectionnez
var Livre1, Livre2, Livre3 : Livre;

Après les instructions suivantes :

Sélectionnez
AssignFile(f,'Bibliotheque.dat');
reset(f);
read(f,Livre1);
read(f,Livre2);
read(f,Livre3);

les structures Livre1 et Livre2 contiendront le livre VB et Livre3 contiendra le livre Gaston.

Lecture jusqu'en fin de fichier▲

De manière générale, on ne connaît pas le nombre d'enregistrements contenus dans un fichier structuré. On utilise alors la fonction eof pour savoir si on est arrivé à la fin du fichier. Voici comment lire séquentiellement un fichier structuré afin d'effectuer un traitement sur chacun de ses enregistrements :

Sélectionnez
var f : file of type;
    x : type;
AssignFile(f,'Bibliotheque.dat');
reset(f);
while not eof(f) do
begin
  read(f,x);
  // Traitement de l'enregistrement x
end;

Exercices▲

Téléchargez les exercices sur le site de l'auteur :

  • Exo-Fichiers-Pascal.pdf

Pour obtenir les corrigés, le téléchargement n'est possible que via un login et un mot de passe, que vous pouvez obtenir en envoyant un mail à l'adresse suivante :

Image non disponible

en précisant un peu qui vous êtes et les raisons pour lesquelles ce cours vous intéresse.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Eric Thirion. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique Lazarus : Alcatîz -

Developpez.com

Nous contacter

Participez

Informations légales

Services

Forum Lazarus

Blogs

Hébergement

Partenaires

PlanetHoster
Ikoula

Copyright © 2000-2014 - www.developpez.com


Programmation avec l'API Windows

Table des matières

  • Les bases d'un programme Windows
    • I. Les bases d'un programme Windows
      • I-A. Création d'une boîte de dialogue
        • I-A-1. Cours théorique
        • I-A-2. Projet N°1
      • I-B. Création d'une fenêtre
        • I-B-1. Cours théorique
        • I-B-2. Projet N°2
      • I-C. Affichage dans une fenêtre
        • I-C-1. Cours théorique
        • I-C-2. Projet N°3
      • I-D. Lecture dans un fichier et boîte de dialogue
        • I-D-1. Cours théorique
        • I-D-2. Projet N°4
      • I-E. Pas à pas pour C++ Builder
        • I-E-1. BCB 6
        • I-E-2. BCB 4
      • I-F. Lecture d'un fichier WAV
        • I-F-1. Cours théorique
        • I-F-2. Projet N°5
      • I-G. Création d'une application console
        • I-G-1. Cours théorique
      • I-H. Fenêtre classique ou ressource ?
        • I-H-1. Cours théorique
  • Les boîtes de dialogue
    • II. Les boîtes de dialogue
      • II-A. Fonctionnement général
        • II-A-1. Cours théorique
      • II-B. Initialisation
        • II-B-1. Cours théorique
      • II-C. Le contrôle 'Edit'
        • II-C-1. Cours théorique
      • II-D. Le contrôle 'Check Box'
        • II-D-1. Cours théorique
      • II-E. Le contrôle 'Radio Button'
        • II-E-1. Cours théorique
      • II-F. Le contrôle 'List Box'
        • II-F-1. Cours théorique
      • II-G. Le contrôle 'Progress bar'
        • II-G-1. Cours théorique
      • II-H. Le contrôle 'Combo box'
        • II-H-1. Cours théorique
      • II-I. Synthèse sur les contrôles
        • II-I-1. Cours théorique
      • II-J. Deux projets récapitulatifs
        • II-J-1. Cours théorique
        • II-J-2. Projet N°6a
      • II-K. MessageBox()
        • II-K-1. Cours théorique
      • II-L. Parcourir l'arborescence
        • II-L-1. Cours théorique
  • Les fenêtres
    • III. Les fenêtres
      • III-A. Fenêtres et boîtes de dialogue
        • III-A-1. Cours théorique
      • III-B. Fonctionnement général d'une fenêtre
        • III-B-1. Cours théorique
      • III-C. Récupération des messages
        • III-C-1. Cours théorique
      • III-D. Création d'une fenêtre
        • III-D-1. Cours théorique
      • III-E. Destruction d'une fenêtre
        • III-E-1. Cours théorique
      • III-F. Contexte d'affichage
        • III-F-1. Cours théorique
      • III-G. Gestion de base d'une fenêtre
        • III-G-1. Cours théorique
      • III-H. Interactions avec l'utilisateur
        • III-H-1. Cours théorique
      • III-I. Les timers
        • III-I-1. Cours théorique
      • III-J. Un projet récapitulatif
        • III-J-1. Cours théorique
      • III-K. Affichage de texte dans une fenêtre
        • III-K-1. Cours théorique
      • III-L. Utilisation de polices personnalisées
        • III-L-1. Cours théorique
      • III-M. Affichage d'une image
        • III-M-1. Cours théorique
      • III-N. Dessin
        • III-N-1. Cours théorique
      • III-O. Fond personnalisé
        • III-O-1. Cours théorique
      • III-P. Commandes systèmes
        • III-P-1. Cours théorique
      • III-Q. Menus
        • III-Q-1. Cours théorique
      • III-R. Création dynamique d'un contrôle
        • III-R-1. Cours théorique
  • Le système de fichier
    • IV. Le système de fichier
      • IV-A. Introduction
        • IV-A-1. Cours théorique
      • IV-B. Création et ouverture d'un fichier
        • IV-B-1. Cours théorique
      • IV-C. Lecture/Écriture dans un fichier
        • IV-C-1. Cours théorique
      • IV-D. Manipulations sur les fichiers
        • IV-D-1. Cours théorique
      • IV-E. Enumération de fichiers
        • IV-E-1. Cours théorique
      • IV-F. Manipulations sur les dossiers
        • IV-F-1. Cours théorique
      • IV-G. Manipulations des chemins
        • IV-G-1. Cours théorique
  • Le multithreading
    • V. Le multithreading
      • V-A. Introduction
        • V-A-1. Cours théorique
      • V-B. Notion de processus et de thread
        • V-B-1. Cours théorique
      • V-C. Partage des tâches
        • V-C-1. Cours théorique
      • V-D. Synchronisation
        • V-D-1. Cours théorique
      • V-E. Premier exemple
        • V-E-1. Cours théorique
      • V-F. Arrêt d'un thread
        • V-F-1. Cours théorique
      • V-G. Récupération des messages
        • V-G-1. Cours théorique
      • V-H. Communication inter-threads
        • V-H-1. Cours théorique
      • V-I. Sections Critiques
        • V-I-1. Cours théorique
      • V-J. Fonctions d'attente
        • V-J-1. Cours théorique
      • V-K. Événements
        • V-K-1. Cours théorique
      • V-L. Sémaphores
        • V-L-1. Cours théorique
      • V-M. Trois projets simples
        • V-M-1. Cours théorique
      • V-N. Performances
        • V-N-1. Cours théorique
  • Annexes
    • VI. Annexes
      • VI-A. Annexe A : Les contrôles
        • VI-A-1. Styles des contrôles
        • VI-A-2. Messages utilisables avec les contrôles
      • VI-B. Annexe B : Fonctions par thèmes


Programmation avec l'API Windows

Table des matières

  • I. Les bases d'un programme Windows
    • I-A. Création d'une boîte de dialogue
      • I-A-1. Cours théorique
      • I-A-2. Projet N°1
    • I-B. Création d'une fenêtre
      • I-B-1. Cours théorique
      • I-B-2. Projet N°2
    • I-C. Affichage dans une fenêtre
      • I-C-1. Cours théorique
      • I-C-2. Projet N°3
    • I-D. Lecture dans un fichier et boîte de dialogue
      • I-D-1. Cours théorique
      • I-D-2. Projet N°4
    • I-E. Pas à pas pour C++ Builder
      • I-E-1. BCB 6
      • I-E-2. BCB 4
    • I-F. Lecture d'un fichier WAV
      • I-F-1. Cours théorique
      • I-F-2. Projet N°5
    • I-G. Création d'une application console
      • I-G-1. Cours théorique
    • I-H. Fenêtre classique ou ressource ?
      • I-H-1. Cours théorique

I. Les bases d'un programme Windows

I-A. Création d'une boîte de dialogue

I-A-1. Cours théorique

Bien comprendre le principe de fonctionnement d'une application Windows est essentiel avant de commencer à programmer. Je vais ici exposer le principe global de fonctionnement d'une application créant une fenêtre.

Tout d'abord, il convient de bien garder à l'esprit qu'un programme fonctionnant sous Windows et gérant une fenêtre doit rester en dialogue permanent avec Windows. Le programme ne connaît (a priori) rien sur son environnement. C'est Windows qui signale à l'application si elle doit redessiner le contenu d'une de ses fenêtres, ou encore si l'utilisateur essaie de fermer la fenêtre.

Cette communication se fait au travers de messages que Windows envoie à chaque fenêtre concernée. C'est au programme d'effectuer la réception de ces messages et de les transmettre aux fonctions gérant les différentes fenêtres. La réception de ces messages ne doit donc pas prendre de retard, et le traitement de chacun des messages doit être bref. En cas de retard, le redessinement des fenêtres n'est donc plus assuré, ce qui a pour conséquence des fenêtres blanches, non déplaçable, similaires à celles des programmes "plantés".

Chaque fenêtre est associée à une fonction ou procédure de fenêtre (Window Proc). Parfois plusieurs fenêtres peuvent être associées à une même procédure. Chaque message reçu est transmis à la procédure correspondante qui se chargera de traiter ce message (redessiner la fenêtre, la redimensionner, afficher un caractère entré par l'utilisateur…).

Une partie du travail de rafraîchissement de la fenêtre est pris en charge par Windows. Le programme n'a à redessiner que la zone client de sa fenêtre (et non pas la barre de titre, les menus éventuels…).

I-A-2. Projet N°1

Objectif : créer une boîte de dialogue et une procédure très simple chargée de la gérer.

Réalisation : la boîte de dialogue sera créée grâce à l'éditeur de ressources de VC++. Le programme sera chargé de la réception des messages et du traitement des plus simples.

Télécharger le projet.

Le projet pas à pas :

Tout d'abord, il s'agit de créer le projet dans VC++ : File/New/Projects/Win32 Application. Donnez ensuite un nom à votre projet et cliquez 'Ok'. Ensuite, sélectionnez 'An empty project' puis 'Finish' et 'Ok'. Nous venons de créer un projet vide, il ne contient aucun fichier de code.

Ajoutons maintenant un fichier de code et fichier de ressources. Allez dans File/New/Files/C++ Source File. Appelez ce fichier main.cpp et validez. Refaites la même opération mais avec un fichier de type Ressource Script nommé res. Fermez le fichier qui s'est ouvert dans l'éditeur. Le script de ressources est associé à un fichier d'en-tête créé et mis à jour automatiquement, mais qu'il convient d'ajouter. Dans le feuillet FileView du panneau de gauche, faites un clic droit sur Header Files et sélectionnez Add Files to Folder. Ajoutez le fichier ressource.h qui doit être dans le même répertoire que votre projet.

Créons à présent le modèle de notre boîte de dialogue. Allez dans le feuillet RessourceView du panneau de travail (à gauche). Faites un clic droit sur res ressources et sélectionnez Insert/Dialog/New. Nous ne modifierons pas cette boîte de dialogue.

Occupons-nous maintenant du code qui va constituer le programme. Tout d'abord, les fichiers d'en-tête, deux sont nécessaires, Windows.h et ressource.h que nous avons inclus précédemment.

Le point d'entrée du programme est la fonction WinMain(). Elle est exécutée automatiquement par Windows lorsque le programme est démarré. Mais avant cette fonction, nous aurons la définition de la procédure de notre fenêtre principale, MainPoc().

Sélectionnez
#include <Windows.h>
#include "ressource.h"

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{

Nous allons maintenant créer la boîte de dialogue grâce à la fonction CreateDialog(). Le paramètre HINSTANCE est un identifiant de notre application, on passe l'identifiant fourni par Windows en paramètre de la fonction WinMain(). On donne ensuite l'identifiant ressource de notre boîte de dialogue. Elle n'a pas de fenêtre parent et la procédure est la fonction MainProc(). La fonction retourne une variable de type HWND qui identifie notre fenêtre. Puis, on affiche cette fenêtre. Toute fenêtre créée est invisible par défaut.

Sélectionnez
   HWND hDlg;
   hDlg=CreateDialog(hInstance,(LPCTSTR)IDD_DIALOG1,NULL,(DLGPROC)MainProc);
   ShowWindow(hDlg,SW_SHOW);

Occupons-nous maintenant de la réception des messages. La réception des messages est prise en charge par la fonction GetMessage(). Cette fonction retourne FALSE dès que le message WM_QUIT est reçu, ce qui indique que l'application doit être fermée.

Sélectionnez
   MSG msg;
   while(GetMessage(&msg,NULL,0,0)==TRUE)
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return 0;
}

Il ne reste plus qu'à définir le comportement de notre fenêtre dans quelques cas simples: elle doit être fermée et l'application quittée si l'utilisateur presse 'ok' ou essaie de quitter. La fermeture de la fenêtre ne signifie pas forcément l'arrêt de l'application. Ces deux comportements correspondent à deux messages distincts: WM_QUIT indique que l'application doit se terminer, WM_CLOSE indique la fermeture de la fenêtre. Mais dans ce cas, on a une fenêtre un peu particulière, puisqu'il s'agit d'une boîte de dialogue. Sa procédure est définie de la même manière que celle d'une fenêtre.

Sélectionnez
LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam)
{
   int Select;
   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDOK:
         EndDialog(Dlg,0);
         PostQuitMessage(0);
         return TRUE;
      case IDCANCEL:
         EndDialog(Dlg,Select);
         PostQuitMessage(0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}

Cette procédure est très simple, elle ne réagit qu'à la pression des boutons 'Ok' et 'Cancel' (le bouton ou la "croix"). En réponse à l'un ou l'autre de ces événements, la procédure demande la fermeture de la boîte de dialogue et termine l'application en envoyant un message WM_QUIT grâce à la fonction PostQuitMessage().

I-B. Création d'une fenêtre

I-B-1. Cours théorique

Dans la première partie de ce chapitre, nous avons vu comment créer une boite de dialogue. La création d'une boite de dialogue grâce à la fonction CreateDialog() est relativement simple puisqu'une grande partie du travail est effectué par CreateDialog().

Nous allons donc voir maintenant comment créer une fenêtre. Cette fenêtre pourra être personnalisée et ne se réferra à aucune ressource. Notre programme n'utilisera donc pas de ressources.

Comme nous avons pu le voir dans la partie précédente, le programme n'intervient en aucun cas dans le dessin de la fenêtre (barre de titre, menu...). Le programme n'est responsable que du redessinement de la zone client de la fenêtre. Comme il existe plusieurs types de fenêtre (avec un menu, sans menu, pouvant être minimisée ou pas...) il est nécessaire que de créer un 'protoype de fenêtre'. Ce prototype est aussi appelé classe de la fenêtre. C'est sur ce prototype que Windows se basera pour créer la fenêtre.

Une fois le prototype de fenêtre créé, nous demanderons à Windows de créer une fenêtre suivant ce prototype. La fenêtre créée devra elle aussi disposer d'une procédure permettant de traiter les messages qu'elle reçoit. Cette procédure est très similaire à celle gérant la boite de dialogue de la partie précédente. Elle ne traite cependant pas exactement les messages de la même manière.

I-B-2. Projet N°2

Objectif : créer une fenêtre sans utiliser de ressource et une procédure permettant sa gestion.

Réalisation : la fenêtre sera créée sans utiliser de ressource et sa procédure traitera les messages les plus simples.

Télécharger le projet.

Le projet pas à pas :

Il s'agit tout d'abord de créer un projet dans Microsoft Visual C++. Le projet sera créé de la même manière que dans la partie précédente, mais sans y ajouter de Ressource Script.

Le point d'entrée WinMain() est un standard à toute application Windows, ce programme débutera donc de la même manière que le précédent.

Sélectionnez
#include <Windows.h>

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{

Il faut maintenant créer une nouvelle classe de fenêtre. Cette classe est créée au moyen de la fonction RegisterClassEx(). La création de cette classe peut paraître fastidieuse et comprend un grand nombre de paramètres. nous n'utiliserons pas tous les paramètres, donc ne vous effrayez pas si vous ne comprenez pas totalement l'utilité de chacun d'eux.

Sélectionnez
   WNDCLASSEX principale;
   principale.cbSize=sizeof(WNDCLASSEX);
   principale.style=CS_HREDRAW|CS_VREDRAW;
   principale.lpfnWndProc=MainProc;
   principale.cbClsExtra=0;
   principale.cbWndExtra=0;
   principale.hInstance=hInstance;
   principale.hIcon=LoadIcon(NULL,IDI_APPLICATION);
   principale.hCursor=LoadCursor(NULL,IDC_ARROW);
   principale.hbrBackground=reinterpret_cast<HBRUSH>(COLOR_WINDOW+1);
   principale.lpszMenuName=NULL;
   principale.lpszClassName="std";
   principale.hIconSm=LoadIcon(NULL,IDI_APPLICATION);
   RegisterClassEx(&principale);

principale.style=CS_HREDRAW|CS_VREDRAW indique que notre fenêtre devra être redessinée en cas de redimensionnement horizontal ou vertical. Nous indiquons ensuite quelle procédure sera chargée de gérer la fenêtre. L'instance de notre application est elle aussi passée en paramètre. Les curseurs ou icônes de notre fenêtre sont ceux par défaut de Windows. La fenêtre n'as pas de menu, la couleur de fond est celle de Windows par défaut. Le nom de la classe de la fenêtre 'std' est un choix personnel. Il n'a d'importance que pour nous. Une fois la structure remplie, un appel à RegisterClassEx() demande à Windows de mémoriser la classe.

Maintenant que nous disposons de notre classe de fenêtre, il ne reste plus qu'a la créer grâce à la fonction CreateWindowEx().

Sélectionnez
   HWND hWnd;
   hWnd=CreateWindowEx(
      WS_EX_CLIENTEDGE,
      "std",
      "Notre fenêtre",
      WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      NULL,
      NULL,
      hInstance,
      NULL
   );

   ShowWindow(hWnd,SW_SHOW);

La réception des messages se fait exactement de la même manière que dans le projet précédent.

Sélectionnez
   MSG msg;
   while(GetMessage(&msg,NULL,0,0)==TRUE)
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return 0;
}

La définition de la procédure de cette fenêtre est la même que dans le programme précédent. Mais son fonctionnement interne est différent.

Tout d'abord, cette fenêtre ne contient pas de bouton. Sa fermeture se fera lors d'un clic sur la 'croix'. Les messages non traités sont passés à DefWindowProc(). Les messages traités seront WM_PAINT et WM_DESTROY. WM_PAINT indique que nous devons redessiner la zone client. Ici le traitement de ce message ne fera rien et pourrait être ignoré. Il est inclus dans le seul but de comprendre le mécanisme de redessinement d'une fenêtre. Le message WM_DESTROY indique que la fenêtre est en train d'être détruite (probablement suite au clic sur la 'croix'). Le fermeture de la fenêtre ne signifie pas forcément l'arrêt de l'application. Mais comme dans ce cas notre application n'est formée que d'une fenêtre, nous demanderons à terminer l'application dans ce cas en postant un message WM_QUIT.

Sélectionnez
LRESULT CALLBACK MainProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam)
{
   HDC hDC;
   PAINTSTRUCT paintst;
   switch (mes)
   {
   case WM_PAINT:
      hDC=BeginPaint(hWnd,&paintst);
      EndPaint(hWnd,&paintst);
      return 0;
   case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
   default:
      return DefWindowProc(hWnd, mes, wParam, lParam);
   }
}

Le traitement du message WM_PAINT peut vous paraître étrange. En effet, pourquoi effectuer un BeginPaint() et un EndPaint() alors que nous ne dessinons rien. La raison est très simple. À partir du moment ou nous décidons de traiter ce message, nous devons assurer un traitement minimum par la suite de ces deux fonctions, même si rien n'est dessiné.

I-C. Affichage dans une fenêtre

I-C-1. Cours théorique

Maintenant que nous savons créer une fenêtre, nous allons voir comment dessiner dans sa zone client. L'affichage dans la zone client d'une fenêtre se fait au niveau du message WM_PAINT. Pour provoquer l'envoi d'un tel message, il faut demander le redessinement d'une zone de la fenêtre. Lors du dessin dans la partie WM_PAINT, il sera impossible d'afficher hors de la zone de redessinement.

Pour dessiner dans une fenêtre, nous allons utiliser un contexte d'affichage. Il est propre à la fenêtre dans laquelle nous dessinons et nous est fourni par Windows. Il identifie en fait la zone de l'écran dans laquelle nous allons dessiner.

I-C-2. Projet N°3

Objectif : créer une fenêtre et afficher l'heure.

Réalisation : la création de la fenêtre se fera de manière identique au projet précédent. Seule sera ajoutée la partie permettant l'affichage de l'heure courante.

Télécharger le projet.

Le projet pas à pas :

La création de la fenêtre se fait exactement de la même manière que dans le projet précédent. Nous allons simplement utiliser les fonctions contenues dans stdio.h.

Sélectionnez
#include <Windows.h>
#include <stdio.h>

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
   WNDCLASSEX principale;
   principale.cbSize=sizeof(WNDCLASSEX);
   principale.style=CS_HREDRAW|CS_VREDRAW;
   principale.lpfnWndProc=MainProc;
   principale.cbClsExtra=0;
   principale.cbWndExtra=0;
   principale.hInstance=hInstance;
   principale.hIcon=LoadIcon(NULL,IDI_APPLICATION);
   principale.hCursor=LoadCursor(NULL,IDC_ARROW);
   principale.hbrBackground=reinterpret_cast<HBRUSH>(COLOR_WINDOW+1);
   principale.lpszMenuName=NULL;
   principale.lpszClassName="std";
   principale.hIconSm=LoadIcon(NULL,IDI_APPLICATION);
   RegisterClassEx(&principale);

   HWND hWnd;
   hWnd=CreateWindowEx(
      WS_EX_CLIENTEDGE,
      "std",
      "Notre fenêtre",
      WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      NULL,
      NULL,
      hInstance,
      NULL
   );
   ShowWindow(hWnd,SW_SHOW);

Comme nous allons afficher l'heure, il est important que nous soyons prévenus de manière régulière. Nous allons demander à Windows de nous avertir toutes les secondes, de manière à afficher l'heure régulièrement. Notre fenêtre recevra un message WM_TIMER toutes les secondes.

Sélectionnez
SetTimer(hWnd,NULL,1000,NULL);

La réception des messages se fait exactement de la même manière que dans le projet précédent.

Sélectionnez
   MSG msg;
   while(GetMessage(&msg,NULL,0,0)==TRUE)
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return 0;
}

La procédure fonctionne de manière identique que précédemment. Nous allons ajouter le traitement du message WM_TIMER qui sera reçu toutes les secondes. Ce message demandera le redessinement d'une zone de l'écran (la zone ou nous afficherons l'heure).

Sélectionnez
LRESULT CALLBACK MainProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam)
{
   HDC hDC;
   PAINTSTRUCT paintst;
   RECT rcClient;
   switch (mes)
   {
      case WM_TIMER:
         rcClient.top=0;
         rcClient.left=0;
         rcClient.right=100;
         rcClient.bottom=50;
         RedrawWindow(hWnd,&rcClient,NULL,RDW_ERASE|RDW_INVALIDATE|RDW_ERASENOW|RDW_NOCHILDREN);
         return 0;

Un message WM_PAINT sera donc reçu toutes les secondes et à chaque fois que la fenêtre doit être redessinée. La fenêtre doit être redessinée si elle est couverte puis découverte par une autre fenêtre (par exemple).

Nous allons afficher l'heure en utilisant une police spécifique. Une fois cette police créée, il est nécessaire d'indiquer que nous allons l'utiliser dans notre contexte d'affichage (HDC). Le contexte d'affichage (ou Device Context) réfère à la zone client de notre fenêtre et contient tous ses paramètres graphiques (disposition sur l'écran, nombre de couleurs affichables…). Il est identifié par la variable hDC qui nous est passée par Windows au travers de la fonction BeginPaint().

Une fois la police créée et le texte dessiné, il convient de détruire la police.

L'heure courante sera récupérée grâce à la fonction GetLocalTime().

Sélectionnez
   case WM_PAINT:
      char buf[256];
      SYSTEMTIME CurrentTime;
      HFONT hFont;
      hFont=CreateFont(20,0,0,0,700,FALSE,FALSE,FALSE,0,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH|FF_DONTCARE,"Comic Sans MS");
      hDC=BeginPaint(hWnd,&paintst);
      SelectObject(hDC,hFont);
      GetLocalTime(&CurrentTime);
      sprintf(buf,"%d : %d : %d",CurrentTime.wHour,CurrentTime.wMinute,CurrentTime.wSecond);
      TextOut(hDC,0,0,buf,strlen(buf));
      EndPaint(hWnd,&paintst);
      DeleteObject(hFont);
      return 0;
   case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
   default:
      return DefWindowProc(hWnd, mes, wParam, lParam);
   }
}

Il est essentiel de bien comprendre le principe d'affichage dans une fenêtre car il est très utilisé par Windows. Quel que soit le cas, l'affichage se fait toujours entre deux fonctions qui indiquent le début et la fin de l'affichage. Il ne faut jamais oublier de libérer le contexte d'affichage après l'avoir utilisé. Ici, la libération du contexte d'affichage se fait par la fonction EndPaint().

I-D. Lecture dans un fichier et boîte de dialogue

I-D-1. Cours théorique

Nous allons maintenant revenir sur un programme utilisant des ressources. En effet, l'utilisation de boites de dialogues créées en ressources permettent bien souvent de gagner un temps précieux pour des programmes de petites tailles et de faible complexité. C'est le cas du programme que nous allons réaliser ici.

Nous allons donc aborder deux thèmes principaux: les boites de dialogues et les contrôles prédéfinis, ainsi que la lecture d'un fichier par des fonctions de l'API Windows, donc sans utiliser les fonctions du C traditionnel. Les contrôles prédéfinis sont les 'edit boxes', 'combo boxes', 'buttons', etc. Leur utilisation est très simple, car ils sont gérés de manière entièrement autonome. Ils envoient à la fenêtre qui les a créés des messages de notification pour indiquer les actions de l'utilisateur : clic sur le bouton, saisie d'un caractère…

La lecture du fichier se fait de manière relativement simple, et fonctionne par un système de HANDLE (comme beaucoup de fonctions de l'API Windows) qu'il faut créer puis détruire.

I-D-2. Projet N°4

Objectif : créer une boite de dialogue permettant la saisie d'un fichier et affichant les 500 premiers octets de ce fichier.

Réalisation : nous allons ici utiliser une ressource pour créer la boite de dialogue. La lecture du fichier se fera au niveau de la procédure de la boite de dialogue, car elle est très brève. Nous utiliserons ici une fonction qui permet de créer la boite de dialogue et de relever les messages, la partie 'main' du programme sera donc très courte (2 lignes).

Télécharger le projet.

Le projet pas à pas :

Nous allons créer un programme avec un fichier de ressources, inclure les fichiers correspondants (voir parties précédentes). La fonction WinMain() est excessivement simple puisque la fonction DialogBox() s'occupe de créer la boîte de dialogue et de récupérer ses messages. Cette fonction retourne seulement une fois que la boite de dialogue est fermée.

Sélectionnez
#include <Windows.h>

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
   DialogBox(hInstance,(LPCTSTR)IDD_MAIN,NULL,(DLGPROC)MainProc);
   return 0;
}

La procédure de la boîte de dialogue est très simple, elle ne gère que trois messages provenant de trois boutons. IDOK est envoyé lors d'un clic sur le bouton OK, IDCANCEL est envoyé lors d'un clic sur la 'croix' et IDC_LIRE est un message envoyé lors d'un clic sur le bouton IDC_LIRE que nous avons créé. Les variables créées en début de procédure serviront à la lecture du fichier que nous verrons après.

Sélectionnez
LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam)
{
   int Select;
   char buf[501];
   HANDLE hFile;
   DWORD Read;
   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDC_LIRE:

La réelle nouveauté de ce programme va se faire ici. Il s'agit tout d'abord de récupérer le nom de fichier entré par l'utilisateur. Comme il est entré dans un contrôle prédéfini, nous allons utiliser la fonction GetDlgItemText() et nous placerons ce nom de fichier dans le buffer créé au début de cette procédure sous le nom de buf. Une fois ce nom de fichier récupéré, nous allons essayer d'ouvrir le fichier donné en lecture. Si nous n'y parvenons pas, un message d'erreur sera affiché grâce à la fonction MessageBox(). Le traitement du message sera alors arrêté.

Remarque: la fonction MessageBox() ne retourne qu'une fois que la boîte de dialogue créée est fermée. Mais la récupération des messages de notre boîte de dialogue est assurée par MessageBox(). Le traitement prolongé de ce message n'est donc pas gênant dans ce cas puisque les messages continueront d'arriver à notre procédure.

Sélectionnez
         GetDlgItemText(Dlg,IDC_FILENAME,buf,256);
         hFile=CreateFile(buf,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
         if(hFile==INVALID_HANDLE_VALUE)
         {
            MessageBox(Dlg,"Erreur, impossible d'ouvrir le fichier spécifié.","Erreur",MB_OK);
            return 0;
         }

Arrivés à ce stade nous savons que le fichier saisi est valide et qu'il a pu être ouvert. Il ne reste plus qu'à lire les 500 premiers octets. Ils seront placés dans le buffer. Comme cette chaîne vas être affichée, elle doit être terminée par un caractère NULL. C'est pour cette raison que buf a été défini comme un tableau de 501 caractères (500 pour la lecture et 1 pour le caractère NULL). Comme la lecture peut faire moins de 500 octets, nous nous baserons sur la valeur retournée par ReadFile() pour ajouter le caractère NULL. Puis nous afficherons ce buffer dans l'edit box créé a cet effet grâce à SetDlgItemText().

Sélectionnez
         ReadFile(hFile,buf,500,&Read,NULL);
         CloseHandle(hFile);
         buf[Read]='';
         SetDlgItemText(Dlg,IDC_TEXT,buf);
         return 0;

Le reste du traitement de la procédure ne présente pas de difficulté particulière. Il est similaire aux traitements précédents. La seule différence notable est lors de la fermeture de la boîte de dialogue. Comme elle a été créée avec la fonction DialogBox() il ne faut pas utiliser PostQuitMesage() pour la quitter mais EndDialog().

Sélectionnez
      case IDOK:
         EndDialog(Dlg,0);
         return TRUE;
      case IDCANCEL:
         EndDialog(Dlg,0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}

Ce programme est relativement simple. La gestion de l'interface graphique ne prend que quelques lignes. Nous voyons donc bien l'intérêt de l'utilisation des ressources et donc contrôles prédéfinis. Sans l'aide des ressources, ce programme aurait été beaucoup plus compliqué à réaliser.

I-E. Pas à pas pour C++ Builder

Nous allons reprendre la 4ème partie : "Lecture dans un fichier et boîte de dialogue." mais adaptée à C++ Builder. Deux procédures sont traitées dans ce document une pour BCB6 et une pour BCB4.

Pour les projets sans boîte de dialogue la procédure est la même sauf qu'il n'y a pas besoin de rajouter de fichiers ressources.

I-E-1. BCB 6

  • Faire "Nouveau". (appelle la boîte de dialogue Nouveaux éléments)
  • Sur la boîte de dialogue "Expert console" :
  • Sur "Type de source" sélectionner "C" ou "C++". (Pour l'exemple cela n'a pas d'importance)
  • Aucune des CheckBox à droite ne doit être cochée.
  • Cliquer sur le bouton "Ok".
  • Maintenant nous avons le code minimum pour ce type d'application :
Sélectionnez
#include <windows.h>
#pragma hdrstop

//---------------------------------------------------------------------------
#pragma argsused
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow)
{
        return 0;}
  • Enregister le projet sous le nom choisi. Ici "main.cpp" et "main.bpr". (Faire "Fichier" puis "Tout enregistrer")
  • J'ai utilisé "Resource WorkShop" (livré avec BCB) pour faire le fichier Dialog1.rc mais il peut se faire avec n'importe quel éditeur de ressource.
  • Puis il faut ajouter le fichier ressource au projet:
  • Sur le menu faire "Projet" puis "Ajouter au projet"
  • Selectionner le type de fichier voulu ici "Fichier ressource (*.rc)"
  • Puis sélectionner le fichier ici "Dialog1.rc"
  • Puis faire Ouvrir
  • Le code source est identique a l'exemple Visual C++ (J'ai mis les mêmes noms d'identificateur)
  • Les source des fichiers "Dialog1.rc" et "ressource.h" sont a la fin de ce document.

Fichier "main.cpp" :

Sélectionnez
#include <windows.h>
#pragma hdrstop //Pas obligatoire
#include "ressource.h"

//---------------------------------------------------------------------------

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

#pragma argsused //ajouté automatiquement mais pas nécéssaire.

WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, in
t nCmdShow)

{
     DialogBox(hInstance,(LPCTSTR)DIALOG_1,NULL,(DLGPROC)MainProc);
     return 0;
}
//---------------------------------------------------------------------------

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam)
{
   int Select;
   char buf[501];
   HANDLE hFile;
   DWORD Read;
   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDC_LIRE:
         GetDlgItemText(Dlg,IDC_FILENAME,buf,256);
         hFile=CreateFile(buf,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
         if(hFile==INVALID_HANDLE_VALUE)
         {
            MessageBox(Dlg,"Erreur, impossible d'ouvrir le fichier spécifié.","Erreur",MB_OK);
            return 0;
         }
         ReadFile(hFile,buf,500,&Read,NULL);
         CloseHandle(hFile);
         buf[Read]='';
         SetDlgItemText(Dlg,IDC_TEXT,buf);
         return 0;

      case IDOK:
         EndDialog(Dlg,0);
         return TRUE;
      case IDCANCEL:
         EndDialog(Dlg,0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}

I-E-2. BCB 4

  • Faire "Nouveau". (appelle la boîte de dialogue Nouveaux éléments)
  • Sur la boîte de dialogue "Nouveaux éléments" Onglet "Nouveau" DbClk sur l'icône "Expert console". (appelle la boîte de dialogue "Expert d'application console")
  • Sur la boîte de dialogue "Expert d'application console" :
  • Sur "Type de fenêtre" sélectionner "Windows (GUI)".
  • Sur "Type d'exécution" sélectionner "EXE".
  • Clicker sur le bouton "Terminer".
  • Maintenant nous avons le code minimum pour ce type d'application :
Sélectionnez
#include <windows.h>
#pragma hdrstop
#include <condefs.h>

//---------------------------------------------------------------------------
#pragma argsused
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
        return 0;
}
  • Enregister le projet sous le nom choisi. Ici "main.cpp". (Faire "Fichier" puis "Tout enregistrer")
  • J'ai utilisé "Resource WorkShop" (livré avec BCB) pour faire le fichier Dialog1.rc mais il peut se faire avec n'importe quel éditeur de ressource.
  • Puis il faut ajouter le fichier ressource au projet:
  • Sur le menu faire "Projet" puis "Ajouter au projet"
  • Sélectionner le type de fichier voulu ici "Fichier ressource (*.rc)"
  • Puis sélectionner le fichier ici "Dialog1.rc"
  • Puis faire Ouvrir
  • Cela va ajouter cette ligne dans le code :
Sélectionnez
USERC("Dialog1.rc");
  • Le reste du code est identique a l'exemple Visual C++ (J'ai mis les mêmes noms d'identificateur)

Fichier "main.cpp" :

Sélectionnez
#include <windows.h>
#pragma hdrstop
#include <condefs.h>
#include "ressource.h"

//---------------------------------------------------------------------------
USERC("Dialog1.rc");
//---------------------------------------------------------------------------

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM );

#pragma argsused //ajouté automatiquement mais pas nécéssaire.
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
{
   DialogBox(hInstance,(LPCTSTR)DIALOG_1,NULL,(DLGPROC)MainProc);
   return 0;
}

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM )
{
   int Select;
   char buf[501];
   HANDLE hFile;
   DWORD Read;
   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDC_LIRE:
         GetDlgItemText(Dlg,IDC_FILENAME,buf,256);
         hFile=CreateFile(buf,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
         if(hFile==INVALID_HANDLE_VALUE)
         {
            MessageBox(Dlg,"Erreur, impossible d'ouvrir le fichier spécifié.","Erreur",MB_OK);
            return 0;
         }
         ReadFile(hFile,buf,500,&Read,NULL);
         CloseHandle(hFile);
         buf[Read]='';
         SetDlgItemText(Dlg,IDC_TEXT,buf);
         return 0;

      case IDOK:
         EndDialog(Dlg,0);
         return TRUE;
     case IDCANCEL:
         EndDialog(Dlg,0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}
  • Autres fichiers
  • Identiques pour BCB4 et BCB6

Fichier "Dialog1.rc"

Sélectionnez
DIALOG_1 DIALOG 14, 30, 212, 227
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "DIALOG_1"
FONT 8, "MS Sans Serif"
{
 CONTROL "Ok", IDOK, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 56, 193, 36, 14
 CONTROL "Cancel", IDCANCEL, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 115, 193, 38, 14
 CONTROL "", IDC_FILENAME, "EDIT", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 26, 18, 91, 12
 CONTROL "", IDC_TEXT, "EDIT", ES_LEFT | ES_MULTILINE | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 18, 49, 175, 119
 CONTROL "Lire", IDC_LIRE, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 136, 18, 50, 14
}

Fichier "Ressource.h"

Sélectionnez
#define DIALOG_1    1

#define IDC_FILENAME 101
#define IDC_TEXT 102
#define IDC_LIRE 103

I-F. Lecture d'un fichier WAV

I-F-1. Cours théorique

A présent, nous allons réaliser un nouveau projet qui sera déjà un programme 'utile' en soi. Evidemment, il restera très sommaire et se contentera de lire un fichier WAV sélectionné par l'utilisateur.

Nous inviterons tout d'abord l'utilisateur à choisir le fichier qu'il veut lire à partir d'une boîte de dialogue 'parcourir'. Une fois que l'utilisateur aura choisi le fichier qu'il désire lire, il pourra choisir de l'écouter. La lecture du fichier WAV se fera grâce à une fonction multimédia fournie par Windows: sndPlaySound(). Cette fonction est entièrement autonome et lira le fichier en arrière plan sans la moindre intervention de la part du programme. Cet automatisme est très pratique puisqu'il permet de lire le fichier sans se soucier de rien. Cependant, les possibilités de cette fonction restent restreintes. On pourra se satisfaire de cette fonction dans quasiment toutes les applications multimédias.

I-F-2. Projet N°5

Objectif : créer une boite de dialogue permettant la saisie d'un fichier WAV puis sa lecture.

Réalisation : nous allons utiliser un fichier ressources qui sera ici très adapté. Le programme ne contiendra que la fonction WinMain() et une procédure pour la boite de dialogue.

Télécharger le projet.

Le projet pas à pas :

Comme dans le programme précédent, nous allons créer un projet d'application Win32, ainsi qu'un fichier de ressources. La fonction WinMain() contiendra simplement l'appel de notre boîte de dialogue. Remarquons tout de même que la fonction sndPlaySound() que nous allons utiliser se trouve dans la librairie winmm.lib qui n'est pas une librairie standard. Il faut donc ajouter cette librairie. Ceci peut être fait grâce au menu Project/Setting/Link/Input. Dans le champ contenant déjà les librairies par défaut, ajoutez winmm.lib. Sans cet ajout, nous obtiendrions une erreur à la compilation indiquant que la fonction ne peut pas être trouvée.

La boîte de dialogue contiendra un champ 'Edit Box' (IDC_NOMDEFICHIER) dans lequel sera affiché le nom du fichier à lire. Nous cocherons la case 'Read Only' dans les options de ce champ de manière à empêcher l'utilisateur de saisir lui-même un nom de fichier. De cette manière, la possibilité de choix erronés se trouve déjà limitée.

Nous ajouterons un bouton 'Parcourir' avec pour identifiant IDC_PARCOURIR et un bouton 'Lire' (IDC_LIRE). Pour quitter, l'utilisateur devra utiliser la 'croix' (ID_CANCEL).

La fonction WinMain() ne diffère pas par rapport au programme précédent (si ce n'est dans les entêtes à inclure). Nous ajouterons une variable globale de manière à pouvoir accéder à l'instance du programme dans toutes les fonctions du programme (et ici dans la procédure de la boîte de dialogue).

Sélectionnez
#include <Windows.h>
#include <mmsystem.h>
#include "ressource.h"

HINSTANCE hInst;

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
   hInst=hInstance;
   DialogBox(hInstance,(LPCTSTR)IDD_MAIN,NULL,(DLGPROC)MainProc);
   return 0;
}

La procédure de la boîte de dialogue n'est pas très compliquée. Il faudra toutefois y ajouter les boutons 'Lire' et 'Parcourir'. Le bouton parcourir sera le plus difficile à ajouter.

Nous allons réserver un buffer de taille MAX_PATH (qui est une constante marquant la taille maximale d'un nom de fichier), ainsi qu'une structure OPENFILENAME qui nous servira pour la boîte de dialogue 'Parcourir'.

Sélectionnez
LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam)
{
   int Select;
   char buf[MAX_PATH];
   OPENFILENAME DlgInfs;

   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDC_PARCOURIR:

La fonction qui affiche la boîte de dialogue 'Parcourir' est GetOpenfileName(). De manière à personnaliser cette boîte de dialogue à nos besoins, nous passons une structure contenant les caractéristiques de la boîte de dialogue. Comme nous n'utiliserons pas une grande partie des membres de cette structure, nous allons tous les mettre à 0 grâce à la fonction memset() du C standard. Ceci constitue une bonne habitude à prendre. Pour plus d'informations sur les paramètres et les possibilités de cette fonction référez-vous à l'aide de fournie par Microsoft.

Sélectionnez
         memset(&DlgInfs,0,sizeof(OPENFILENAME));

         DlgInfs.lStructSize=sizeof(OPENFILENAME);
         DlgInfs.hwndOwner=Dlg;
         DlgInfs.hInstance=hInst;
         DlgInfs.lpstrFilter="Fichiers WAV*.WAV";
         DlgInfs.lpstrFile=buf;
         DlgInfs.nMaxFile=MAX_PATH;
         DlgInfs.lpstrTitle="Choisissez le fichier à lire.";
         DlgInfs.Flags=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST;
         if(GetOpenFileName(&DlgInfs))
            SetDlgItemText(Dlg,IDC_NOMDEFICHIER,buf);
         return TRUE;
Lorsque l'utilisateur pressera le bouton 'Lire', il faudra récupérer le nom de fichier saisi dans le champ 'IDC_NOMDEFICHIER' et le lire grâce à la fonction sndPlaySound().
      case IDC_LIRE:
         GetDlgItemText(Dlg,IDC_NOMDEFICHIER,buf,MAX_PATH);

         sndPlaySound(buf,SND_ASYNC);
         return TRUE;
Il ne reste plus qu'à gérer la sortie de la boîte de dialogue. Dans le cas ou l'utilisateur quitte le programme, il faut stopper la lecture du son en cours. Pour cela on utilisera encore la fonction sndPlaySound() avant de quitter.
      case IDCANCEL:
         sndPlaySound(NULL,NULL);
         EndDialog(Dlg,0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}

Ce programme ne présente aucune difficulté particulière. Il se suffit à lui-même bien qu'étant particulièrement pauvre. Ceci permet de donner une idée de la gestion des boîtes de dialogues qui seront dans le futur intégrées à un programme bien plus important. Bien que la gestion de telles boîtes de dialogue constitue un détail dans un programme plus important, il est nécessaire de bien comprendre leur fonctionnement de manière à ne pas perdre de temps et pouvoir créer rapidement une interface graphique agréable.

I-G. Création d'une application console

I-G-1. Cours théorique

Avant de s'intéresser aux applications consoles, il convient de bien comprendre de quoi il s'agit. Une application console n'est pas un programme DOS. C'est un programme Windows utilisant les fonctions de l'API Windows mais qui s'affiche dans une console DOS au lieu d'une fenêtre classique. Un tel programme ne peut pas être démarré en mode DOS réel.

L'avantage de ces programmes est qu'il est inutile de se préoccuper de l'affichage de la fenêtre puisque toutes les interventions sur la fenêtre sont prises en compte par le système. Ce type d'application est très utile pour de petits utilitaires fonctionnant en ligne de commande (bien qu'ils paraissent austères !).

Cependant, ces programmes fonctionnent de la même manière que les applications DOS (pour ce qui est de l'entrée sortie sur l'écran). On pourra donc utiliser les fonctions printf() et scanf() du C standard pour afficher des données à l'écran. De même les fonctions de flux standard C++ cin et cout sont utilisables.

Cette partie ne fera pas l'objet d'un projet, mais les applications console seront utilisées plus tard pour illustrer des programmes simples et ne nécessitant pas ou peu de communications avec l'utilisateur, de manière à réduire la taille des programmes.

I-H. Fenêtre classique ou ressource ?

I-H-1. Cours théorique

Maintenant que nous avons créé et utilisé ces deux types de fenêtres, il convient de s'interroger sur leur utilisation. Faut-il préférer les ressources ou plutôt les fenêtres classiques ? Quels sont les avantages des une et des autres ?

Les fenêtres créées à partir de ressources (souvent les boîtes de dialogue) sont évidemment bien plus rapides à créer. De plus leur utilisation nécessite beaucoup moins de code. Dans ce cas, pourquoi continuer à utiliser des fenêtres classiques créées avec l'API Windows ?

Si les ressources peuvent séduire, il faut cependant s'en méfier. Elles conviennent très bien pour des boîtes de dialogue invitant l'utilisateur à saisir des valeurs, ou à définir certains paramètres. C'est d'ailleurs leur rôle principal. En les utilisant dans ce contexte on gagne un temps très important par rapport à des fenêtres créées “manuellement”. Cependant, l'affichage dans une boîte de dialogue ne se prête que peu à la personnalisation.

Pour afficher des données comme des images, des polices personnalisées ou autre, il reste préférable d'utiliser une fenêtre classique. De même si la fenêtre doit gérer des interventions utilisateur comme une touche entrée, un clic, il faut alors passer à une fenêtre classique. Dans ces cas, la gestion automatisée devient en fait un obstacle à l'interception de ces événements.

Avant de se lancer dans la programmation d'une fenêtre classique ou au contraire de fenêtres ressources, il faut donc réfléchir à l'utilisation qui sera faite de ces fenêtres. Ni l'une ni l'autre de ces méthodes ne sont à bannir. Au contraire, il faut connaître les deux et savoir utiliser l'une ou l'autre dès qu'il le faut. La facilité des ressources ne doit pas en faire un réflexe systématique.

Bien entendu on ne peut pas établir des caractéristiques types pour lesquelles il faut absolument utiliser tel ou tel type de fenêtre. Cette décision doit prendre en compte les exigences du programme... Je ne me risquerais pas à dresser une liste exhaustive de tous les cas ou l'utilisation de telle ou telle méthode est préférable mais j'invite seulement le programmeur à réfléchir avant d'utiliser les ressources.


Programmation avec l'API Windows

Table des matières

  • II. Les boîtes de dialogue
    • II-A. Fonctionnement général
      • II-A-1. Cours théorique
    • II-B. Initialisation
      • II-B-1. Cours théorique
    • II-C. Le contrôle 'Edit'
      • II-C-1. Cours théorique
    • II-D. Le contrôle 'Check Box'
      • II-D-1. Cours théorique
    • II-E. Le contrôle 'Radio Button'
      • II-E-1. Cours théorique
    • II-F. Le contrôle 'List Box'
      • II-F-1. Cours théorique
    • II-G. Le contrôle 'Progress bar'
      • II-G-1. Cours théorique
    • II-H. Le contrôle 'Combo box'
      • II-H-1. Cours théorique
    • II-I. Synthèse sur les contrôles
      • II-I-1. Cours théorique
    • II-J. Deux projets récapitulatifs
      • II-J-1. Cours théorique
      • II-J-2. Projet N°6a
    • II-K. MessageBox()
      • II-K-1. Cours théorique
    • II-L. Parcourir l'arborescence
      • II-L-1. Cours théorique

II. Les boîtes de dialogue

II-A. Fonctionnement général

II-A-1. Cours théorique

Avant de se lancer dans la programmation d'applications plus complexes, il est nécessaire de bien comprendre le schéma de fonctionnement de la boîte de dialogue. Il se rapproche du fonctionnement d'une fenêtre classique sans pour autant être le même. Il conviendra donc d'éviter les analogies entre ces deux types de fenêtres.

La procédure qui gère une boîte de dialogue peut être excessivement simple. Pour toute notification, Windows appelle la procédure et lui demande de traiter le message. Si elle est en mesure de le traiter, elle doit retourner TRUE. Dans le cas contraire elle retourne FALSE et Windows effectuera le traitement par défaut pour le message. Le minimum est donc d'assurer la fermeture de la boite de dialogue grâce à la fonction EndDialog(). En effet, la fermeture de la boite de dialogue ne fait pas partie du traitement par défaut des messages, ce qui est logique puisqu'il est impossible à Windows de 'deviner' le bouton qui doit quitter la boîte de dialogue.

Dès qu'une boite de dialogue traite un message, elle doit donc retourner TRUE (sauf indication contraire). Remarquez la structure du switch avec le default qui retourne FALSE. Dès qu'un message n'est pas traité, la procédure retourne donc FALSE.

On peut considérer le fonctionnement d'une boîte de dialogue en trois temps. Tout d'abord l'initialisation. C'est à ce moment que les différents contrôles sont initialisés. Par exemple, le statut des 'check boxes' sera défini. Cette phase est généralement indispensable, mais dans certains cas, une boîte de dialogue n'aura besoin d'aucune valeur par défaut. L'initialisation se fait avant que la boite de dialogue ne soit affichée, grâce à un message (WM_INITDIALOG). Si ce message n'est pas traité, c'est à dire si la procédure ne contient pas de case WM_INITDIALOG, aucun champ n'aura de valeur particulière.

La deuxième phase de fonctionnement d'une boite de dialogue est celle durant laquelle l'utilisateur effectue les modifications qu'il désire. Il modifie des champs, 'coche' des options… Ce traitement peut se faire de manière entièrement autonome si aucune action particulière n'est nécessaire. Cette phase ne se reflétera donc pas forcément dans la procédure. Dans certains cas cette phase sera inutile, par exemple pour un message d'erreur.

Une fois que l'utilisateur a fait les modifications qu'il désire, il va falloir les 'récupérer'. Le statut de tous les contrôles doit être analysé de manière à modifier les variables au sein même du programme. En effet, les modifications effectuées par l'utilisateur ne se reflètent en rien au niveau des variables du programme. Une fois que le programme à mis à jour toutes les variables nécessaires, la boite de dialogue peut se terminer.

II-B. Initialisation

II-B-1. Cours théorique

L'initialisation d'une boîte de dialogue se fait grâce au message WM_INITDIALOG. Si une boîte de dialogue ne désire pas effectuer de traitement particulier à sa création, elle ignore simplement ce message. Lorsque ce message est envoyé à la boite de dialogue, celle-ci n'est pas encore affiché. C'est à ce moment que les valeurs par défaut des contrôles sont définies. Ces valeurs seront définies grâce à des fonctions de l'API Windows.

De plus, comme la fenêtre n'est pas encore visible, il est possible de modifier sa taille, sa position, sans que cela apparaisse à l'utilisateur (fonction SetWindowPos() par exemple).

La valeur retournée après le traitement du message WM_INITDIALOG détermine si le champ par défaut aura ou non le focus. Le focus est en fait l'entrée clavier. Si un champ possède le focus et qu'une touche est tapée au clavier, c'est à ce champ et à lui seul que cela sera signalé. Dans un champ demandant un mot de passe (par exemple), il est très utile de passer la focus au champ par défaut, de manière à éviter à l'utilisateur de devoir cliquer dans le champ avant de saisir sont mot de passe. Pour que Windows donne le focus au champ par défaut, la procédure doit retourner TRUE. Dans le cas contraire, elle doit retourner FALSE.

II-C. Le contrôle 'Edit'

II-C-1. Cours théorique

Ce contrôle à déjà été vu dans les projets précédents. C'est un des plus simples à utiliser. Comme pour tous les contrôles, on peut distinguer deux aspects à l'utilisation de celui-ci. Le premier aspect est son apparence. Elle est modifiée grâce à l'éditeur de ressources du compilateur. Si vous utilisez Visual C++, il vous suffit de double cliquer sur le contrôle pour accéder à ses propriétés. La deuxième partie du fonctionnement de ce contrôle se fait au travers de la procédure qui gère la boîte de dialogue. Elle consiste en la définition ou la récupération de l'état du contrôle (ici le texte qu'il contient).

Avant d'utiliser le contrôle, il faut donc définir son apparence, ainsi qu'un identifiant, permettant au programme de l'identifier. L'identifiant d'un contrôle est généralement de la forme IDC_ suivi du nom du contrôle tout en majuscules. Le compilateur se charge de maintenir à jour un fichier définissant toutes les constantes utilisées. Pour Visual C++, ce fichier est ressource.h.

Il est possible de personnaliser de nombreuses caractéristiques pour un contrôle de type 'Edit'. Voici une liste regroupant les styles les plus utilisés. Cette liste n'est pas exhaustive, référez-vous à l'annexe A pour plus de précisions. Si vous utilisez un éditeur de ressources, vous devriez pouvoir modifier les styles grâce à une interface graphique. Si éditez vos ressources en mode texte, les styles sont mis entre crochets.

  • Read Only [ES_READONLY] : afficher le contrôle sur fond gris. Il est impossible de modifier le texte, mais il peut toutefois être sélectionné et copié.
  • Disabled : le contrôle est désactivé. Il apparaît en gris et il est impossible de sélectionner le texte. Cette option ne sera accessible qu'à partir d'un éditeur de ressources, car elle ne constitue pas un style. Pour désactiver un contrôle, utilisez la fonction EnableWndow() lors de l'initialisation de la boîte de dialogue.
  • Number [ES_NUMBER] : indique que l'on ne peut saisir que des chiffres dans ce contrôle. Le point n'est pas accepté. Pour entrer un nombre à virgule, il faudra donc utiliser un contrôle classique.
  • Password [ES_PASSWORD] : affiche des étoiles à la place des caractères saisis. Le texte ne peut pas être copié.
  • Multiline [ES_MULTILINE] : indique que le contrôle peut contenir plusieurs lignes. Pour effectuer un retour à la ligne, il faut afficher le caractère \r\n. Au clavier, il faut taper CTRL+ENTER.
  • AutoHScroll [ES_AUTOHSCROLL] - AutoVScroll [ES_AUTOVSCROLL] : indique si le contrôle doit défiler horizontalement ou verticalement. Pour permettre à un contrôle de défiler verticalement il faut désactiver le défilement horizontal.

Une fois que l'apparence du contrôle est convenable, il faut pouvoir lui assigner une valeur ou récupérer la valeur entrée par l'utilisateur. Ces manipulations se font au travers de quatre fonctions :

  • SetDlgItemInt() : permet de définir la valeur d'un contrôle grâce à un entier. Il est possible d'indiquer si cet entier est signé ou non. L'identifiant du contrôle est celui définit dans les ressources (IDC_... en général).
  • GetDlgItemInt() : permet de récupérer la valeur numérique d'un contrôle. Cette fonction échouera si le contrôle contenait du texte.
  • SetDlgItemText() : permet d'afficher une chaîne de caractères dans le contrôle. Cette chaîne de caractères peut à priori avoir n'importe quelle longueur.
  • GetDlgItemText() : permet de récupérer la chaîne de caractères contenue dans le contrôle. Il est indispensable de préciser la longueur maximale de la chaîne qui sera récupérée (elle correspond à la taille du buffer dans lequel elle sera stockée).

II-D. Le contrôle 'Check Box'

II-D-1. Cours théorique

Le contrôle 'Check Box' fait lui aussi partie de l'un des plus simples. Il peut contenir trois états, coché, non coché ou indéterminé. On peut lui attribuer différents styles qui modifient son apparence et parfois son fonctionnement. Par défaut, ce contrôle ne peut être que dans deux états, coché ou non coché. En modifiant le style on peut permettre à l'utilisateur de spécifier un troisième état, indéterminé, qui correspond à une case cochée mais grisée. Voici une liste des styles principaux pouvant être appliqués à ce contrôle (les styles modifiant l'apparence graphique du contrôle ne sont pas traités ici). Pour une liste exhaustive des différents types consultez l'annexe A.

  • Tri-state [BS_3STATE] [BS_AUTO3STATE] : le contrôle peut prendre trois états au lieu de deux.
  • Auto : [BS_AUTO3STATE] [BS_AUTOCHECKBOX] : permet de créer un contrôle dont l'état est modifié automatiquement lorsque l'utilisateur l'utilise. Si ce style n'est pas activé, la boîte de dialogue reçoit un message WM_COMMAND et doit elle-même modifier l'état du contrôle.

Comme on pouvait s'y attendre, il suffit de deux fonctions pour le manipuler, une pour modifier son état, et l'autre pour récupérer son état.

  • CheckDlgButton() : permet de modifier l'état du contrôle. Il suffit de spécifier la boite de dialogue qui le contient ainsi que son identifiant et un paramètre commandant l'état du contrôle. Les trois états possibles sont BST_CHECKED, BST_INDETERMINATE et BST_UNCHECKED. Le contrôle sera redessiné automatiquement, c'est à dire que lorsque la fonction retournera, l'état graphique du contrôle aura été modifié.
  • IsDlgButtonChecked() : permet de récupérer l'état du contrôle. Cette fonction retourne un entier non signé qui peut prendre une des trois valeurs présentées ci-dessus.

L'utilisation de contrôles 'Check box' se résume donc à peu de choses. Elle pourra être mise en parallèle avec les contrôles 'Radio buttons', qui fonctionnement de manière similaire, mais avec un système de groupes.

II-E. Le contrôle 'Radio Button'

II-E-1. Cours théorique

Le contrôle 'Radio Button' est assez similaire au contrôle 'Check Box', si ce n'est qu'un seul de ces contrôles peut être coché à la fois. Il se présente généralement sous forme d'un rond à cocher. Bien entendu, vous pouvez utilisez plusieurs de ces contrôles dans un même fenêtre et créer des groupes, de sorte qu'un seul des contrôle par groupe ne puisse être sélectionné, et non pas un seul pour l'ensemble de la fenêtre.

Si vous désirez créer seulement un groupe dans une fenêtre, il est inutile de créer un groupe. En effet, par défaut, un seul contrôle peut être sélectionné dans l'ensemble de la fenêtre.

Si vous utilisez l'éditeur de ressources de Visual, la création de groupes est simplifiée. Pour créer un groupe de radio buttons, il faut en insérer un, et lui mettre le style 'Group'. Puis on insère les autres radios du groupe mais sans mettre le style 'Group'. Pour créer un nouveau groupe, il suffit de refaire la même manipulation autant de fois que nécessaire.

Si vous éditez vos ressources en mode texte, le principe est le même. On met le style WS_GROUP au premier des radios puis on insère les autres à la suite.

L'ensemble des styles est décrit dans l'annexe A. La plupart modifient seulement l'apparence graphique du contrôle et ne sont pas très difficiles à utiliser. Retenez simplement un style assez important :

  • Auto [BS_AUTORADIOBUTTON] : ce contrôle indique que les radios sont gérés automatiquement. Vous n'avez pas à demander de décocher les radios lorsqu'un autre est coché. Généralement, ce style est toujours utilisé, sauf cas très particuliers.

Pour cocher ou décocher les radios, il faut utiliser les fonctions ChekDlgButton() et IsDlgButtonChecked(). Ces fonctions sont identiques à celles utilisées avec les contrôles 'Check Box'. Bien entendu, le style indéterminé n'est pas disponible ici. Remarquons que lorsque vous utilisez CheckDlgButton(), les autres radios ne sont pas décochés, même en utilisant le style Auto. Enfin, vous pouvez utilisez la fonction CheckRadioButton() pour cocher un radio et décochez les autres. Pour cela, les radios doivent avoir étés créés dans l'ordre (les identifiants doivent être en ordre croissant). Il suffit ensuite de passer l'identifiant du premier radio, celui du dernier, ainsi que le radio à sélectionner.

II-F. Le contrôle 'List Box'

II-F-1. Cours théorique

Le contrôle 'List Box' est plus complexe que ceux étudiés précédemment. Il ne permet pas seulement de contenir un état entré par l'utilisateur, il permet d'afficher des informations sous forme de liste, de trier la liste par ordre alphabétique… Ce contrôle aura donc de multiples utilisations, d'autant plus qu'il est relativement simple à utiliser. Il ne se manipule pas grâce à des fonctions mais au travers de messages, qui seront envoyés par l'intermédiaire de la fonction SendMessage().

La liste peut être présentée sous deux formes : soit en une seule colonne et avec un défilement vertical, soit sur plusieurs colonnes et avec un défilement horizontal. Les colonnes auront obligatoirement la même taille. Elles ne peuvent pas comporter d'en-têtes.

Voici les styles applicables à ce contrôle. Pour une liste exhaustive, consultez l'annexe A. Remarquez que par défaut, c'est la sélection simple qui est utilisée.

  • Sort [LBS_SORT] : détermine si le contrôle trie ou non la liste par ordre alphabétique.
  • Multi-Column [LBS_MULTICOLUMN] : permet d'afficher la liste sur une ou plusieurs colonnes. Si ce style est activé, il faut utiliser un défilement horizontal.
  • Horizontal Scroll [WS_HSCROLL] : défilement horizontal de la liste.
  • Vertical Scroll [WS_VSCROLL] : défilement vertical de la liste.
  • Multiple Selection [LBS_MULTIPLESEL] : autorise la sélection multiple. L'utilisateur peut sélectionner les éléments de la liste en cliquant dessus.
  • Extended Selection [LBS_EXTENDEDSEL] : autorise la sélection multiple. L'utilisateur sélectionne les éléments de la liste grâce aux touches SHIFT et CTRL.
  • No Selection [LBS_NOSEL] : interdit toute sorte de sélection.

L'utilisation du contrôle est relativement simple. Nous ne verrons ici que les tâches élémentaires, ajout et suppression d'un élément, récupération de la sélection courante. La méthode de récupération de la sélection courante dépend bien entendu du mode de sélection choisi. Pour une sélection unique, cette action est simple. Elle s'avère un peu plus délicate pour un contrôle à sélections multiples. L'ensemble des messages utilisables avec ce contrôle est présenté dans l'annexe A. Voici les messages les plus simples :

  • LB_ADDSTRING : ajoute un élément à la liste et demande le tri de la liste si celle-ci à le style 'Sort'.
  • LB_INSERTSTRING : ajoute un élément à la liste et le place à une position déterminée. Après l'insertion, la liste n'est pas triée.
  • LB_SETITEMDATA : permet d'associer une valeur 32 bits à un élément de la liste. Cette valeur peut par exemple être un pointeur sur les données que représente cet élément.
  • LB_DELETESTRING : supprime l'élément désigné.
  • LB_GETCURSEL : retourne la sélection courante pour un contrôle à sélection unique.
  • LB_GETSELITEMS : place dans un tableau l'ensemble des éléments sélectionnés dans un contrôle à sélections multiples.
  • LB_SETCURSEL : modifie la sélection courante dans un contrôle à sélection unique.
  • LB_GETCOUNT : retourne le nombre total d'éléments contenus par le contrôle.

II-G. Le contrôle 'Progress bar'

II-G-1. Cours théorique

Le contrôle 'Progress Bar' est l'un des plus simples à utiliser. Ses styles étant définis au niveau ressources, l'utilisation se borne à la définition de la position. Pour cela, il faut tout d'abord donner deux valeurs numériques qui constituent le minimum et le maximum de la barre de progression. Une fois cela fait, il ne reste plus qu'à fournir une position courante.

Avant de pouvoir utiliser ce contrôle, il faut penser à l'initialiser. On doit placer en début de programme un appel à la fonction InitCommonControls() ou à la fonction InitCommonControlsEx(), de manière à forcer le chargement des dll permettant d'utiliser ce contrôle. L'appel à l'une de ces deux fonctions peut être fait à n'importe quel moment (mais avant d'utiliser le contrôle) et ne doit être fait qu'une seule fois. Si cet appel n'est pas fait, la boîte de dialogue de s'affichera pas.

Les styles pouvant être utilisés avec ce contrôle sont simples, ils sont donnés dans l'Annexe A.

Voici les messages utilisés pour commander ce contrôle :

  • PBM_SETRANGE permet de définir les valeurs numériques du minimum et du maximum pouvant être atteints. Les valeurs de cet intervalle, tout comme la position courante sont des entiers, il faut donc prévoir un intervalle assez large pour ne pas que l'on distingue de saccades lors de la progression. Cet intervalle doit donc être au moins de la taille (en pixels) de la barre. De manière générale, on peut opter pour un intervalle 0 - 1000.
  • PBM_SETPOS définit la position actuelle de progression. Cette valeur doit bien entendu être comprise dans l'intervalle fourni par le message PBM_SETRANGE.

II-H. Le contrôle 'Combo box'

II-H-1. Cours théorique

Le contrôle 'Combo Box' est l'un des plus complexes. Il peut servir dans différents 'modes de fonctionnement'. On peut l'utiliser pour permettre à l'utilisateur de faire un choix dans un menu déroulant ou permettre à l'utilisateur d'entrer un choix personnalisé. Nous ne verrons ici que l'utilisation de ce contrôle en tant que menu déroulant permettant à l'utilisateur de faire un choix dans une liste prédéterminée. C'est l'utilisation la plus pratique de ce contrôle, car elle permet de s'assurer que le choix fait est valide.

Voici un aperçu des styles applicables à ce contrôle :

  • CBS_DROPDOWN est le style correspondant au type de 'Combo box' que nous avons décrit. La liste est affichée si l'utilisateur clique sur le contrôle et elle ne peut pas être modifiée.
  • CBS_SORT permet d'activer le tri automatique du contrôle par ordre alphabétique.

Pour utiliser ce contrôle, vous pouvez utiliser les messages suivants :

  • CB_ADDSTRING permet d'ajouter une entrée à la liste. La liste sera triée si le style CBS_SORT est utilisé.
  • CB_SETCURSEL permet de définir la sélection courante.
  • CB_GETCURSEL permet de retourner la sélection courante.
  • CB_SETITEMDATA permet d'associer une valeur 32 bits à une entrée. Cette valeur peut être utilisée pour identifier les entrées si la liste est triée par exemple.
  • CB_GETITEMDATA retourne la valeur 32 bits associée avec l'entrée spécifiée.

Pour déterminer la taille de ce contrôle (notamment la hauteur de la liste), on pourra utiliser la fonction SetWindowPos() qui sera vue plus tard.

II-I. Synthèse sur les contrôles

II-I-1. Cours théorique

Les contrôles qui ont été présentés précédemment l'ont été de manière brève. Ce tutoriel ne prétend absolument pas fournir une référence exhaustive de toutes les possibilités d'utilisation des contrôles. Cette présentation des contrôles les plus courants permettra simplement de se familiariser avec leur utilisation. En effet, quel que soit le type de contrôle utilisé, la méthode pour le manipuler reste toujours la même.

Une fois le principe de fonctionnement des contrôles compris, il sera facile de l'étendre à une utilisation plus complexe. Les contrôles prédéfinis offrent une excellente manière de guider l'utilisateur et de le limiter. De plus, leur utilisation demande relativement peu de code et permet de créer rapidement une interface agréable. Les contrôles offrent de nombreuses fonctionnalités qu'il faut savoir exploiter.

On remarquera cependant que ces contrôles sont assez difficilement personnalisables. Pour créer une interface graphique plus poussée ou plus personnalisée, il faudra donc recourir à d'autres méthodes que des contrôles. Il est tout de même possible de personnaliser les contrôles (couleurs, etc.) mais ces méthodes ne permettant une personnalisation complète de l'interface.

Il faut tout de même rappeler que les ressources ne sont pas le seul moyen de mettre en place les contrôles, ils constituent simplement un 'raccourci' pour leur utilisation. Tous les contrôles peuvent être intégrés de manière dynamique dans une fenêtre ou un boîte de dialogue en utilisant la fonction CreateWindowEx(). Dans le cas d'une interface dynamique, c'est à dire ne se présentant pas systématiquement sous la même forme, l'utilisation des ressources est fortement déconseillée car elle 'fixe' les contrôles au moment de la compilation. La création des contrôles au moment de l'affichage permet donc une plus grande souplesse, bien qu'exigeant une mise en place plus contraignante.

II-J. Deux projets récapitulatifs

II-J-1. Cours théorique

Voici 2 projets qui présentent des utilisations extrêmement simples de quelques contrôles élémentaires. Seul le projet 06a sera décrit ici, le second étant très similaire.

II-J-2. Projet N°6a

Ces projets présentent une utilisation extrêmement simple de quelques contrôles élémentaires. Aucune vérification n'est faite quant à la validité des entrées faites par l'utilisateur. Remarquons que du fait des contrôles utilisés, l'utilisateur ne peut pas fournir de sélection non valide (à part entrer un nom vide). C'est d'ailleurs pour ça qu'il est indispensable d'initialiser les contrôles 'Radio' ou 'Combo box' car sans cela, on pourrait avoir une sélection non valide (aucune sélection en fait). Les contrôles sont bien entendus initialisés lors du message WM_INITDIALOG (à ce moment, la boîte de dialogue n'est pas encore affichée).

La liste n'est modifiée qu'au moment où l'utilisateur presse le bouton 'Ajouter'. A ce moment, on récupère le statut de tous les contrôles que l'on a placés et on vérifie éventuellement la validité des données entrées. Si tout est correct, on ajoute les données voulues à la liste. Ici, on aurait quelques peines à récupérer les informations contenues dans la liste au moment de la validation de la boîte de dialogue. Pour faciliter cela, il faudrait stocker chaque ligne dans une structure appropriée. On associerait ensuite à chaque entrée de la liste son index dans notre tableau grâce au message LB_SETITEMDATA.

Télécharger les projets commentés [VC++] : Projet 06a - Projet 06b.

Télécharger les projets commentés [BCB] : Projet 06a - Projet 06b.

II-K. MessageBox()

II-K-1. Cours théorique

La fonction MessageBox() permet d'afficher une boite de dialogue prédéfinie contenant le texte spécifié. Elle est très utile pour afficher des messages d'erreurs, informer l'utilisateur. Elle retourne l'identifiant du bouton qui a été pressé pour la quitter. Cette fonction ne permet quasiment aucune personnalisation de la boîte de dialogue qu'elle affiche, elle n'est donc réellement utile que dans le cas de messages envoyés à l'utilisateur, souvent de manière informative avec pour seul choix le bouton 'Ok'.

Les types de boites de dialogue pouvant être affichés sont :

  • MB_ABORTRETRYIGNORE
  • MB_OK
  • MB_OKCANCEL
  • MB_RETRYCANCEL
  • MB_YESNO
  • MB_YESNOCANCEL

Les icônes suivantes peuvent être affichées sur la boîte de dialogue :

  • MB_ICONEXCLAMATION
  • MB_ICONWARNING
  • MB_ICONINFORMATION
  • MB_ICONASTERISK
  • MB_ICONQUESTION
  • MB_ICONSTOP
  • MB_ICONERROR
  • MB_ICONHAND

La fonction retournera l'une des valeurs suivantes :

  • IDABORT
  • IDCANCEL
  • IDIGNORE
  • IDNO
  • IDOK
  • IDRETRY
  • IDYES

II-L. Parcourir l'arborescence

II-L-1. Cours théorique

Pour permettre à l'utilisateur de sélectionner un dossier ou un fichier, on utilisera respectivement les fonctions SHBrowseForFolder() et GetOpenFileName(). Ces deux fonctions affichent les boîtes de dialogue standard de Windows pour parcourir l'arborescence à la recherche d'un dossier ou d'un fichier. Notons que la constante MAX_PATH désigne la longueur standard pour un chemin. Les buffers chargés de recevoir les noms de fichiers ou les chemins sont supposés faire au moins cette taille.

Voici un exemple d'utilisation de la fonction SHBrowseForFolder() :

Sélectionnez
BROWSEINFO bi;
LPITEMIDLIST Item;
// Ici, la taille du buffer ne peut pas être passée
// buffer est supposé être de taille MAX_PATH (ou plus)
char buffer[MAX_PATH];

// On met tous les champs inutilisés à 0
memset(&bi,0,sizeof(BROWSEINFO));
// hDlg est le HWND de la boite de dialogue qui demande l'ouverture
// Ou NULL si la boite de dialogue n'a pas de fenêtre parent
bi.hwndOwner=Dlg;
// Contient le répertoire initial ou NULL
bi.pidlRoot=NULL;
bi.pszDisplayName=buffer;
bi.lpszTitle="Répertoire courant";
bi.ulFlags=NULL;
bi.lParam=NULL;
Item=SHBrowseForFolder(&bi);
if(Item!=NULL)
{
    // buffer contient le nom du répertoire sélectionné
    SHGetPathFromIDList(Item,buffer);
    // buffer contient le chemin complet de la sélection
}

Voici un exemple d'utilisation de la fonction GetOpenFileName() :

Sélectionnez
OPENFILENAME st;
char buffer[MAX_PATH];

// Pas de fichier par défaut
buf[0]='';
// On met tous les champs inutilisés à 0
memset(&st,0,sizeof(OPENFILENAME));
st.lStructSize=sizeof(OPENFILENAME);
// hDlg est le HWND de la boite de dialogue qui demande l'ouverture
// Ou NULL si la boite de dialogue n'a pas de fenêtre parent
st.hwndOwner=hDlg;
// La syntaxe est : Description1Filtre1Description2Filtre2
st.lpstrFilter="Executables - Fichiers de commandes*.exe;*.bat";
st.lpstrFile=buffer;
st.nMaxFile=MAX_PATH;
st.lpstrTitle="Recherche de l'exécutable";
st.Flags=NULL;
// Contient le répertoire initial ou NULL
st.lpstrInitialDir=NULL;
if(GetOpenFileName(&st))
    // buffer contient notre chemin


Programmation avec l'API Windows

Table des matières

  • III. Les fenêtres
    • III-A. Fenêtres et boîtes de dialogue
      • III-A-1. Cours théorique
    • III-B. Fonctionnement général d'une fenêtre
      • III-B-1. Cours théorique
    • III-C. Récupération des messages
      • III-C-1. Cours théorique
    • III-D. Création d'une fenêtre
      • III-D-1. Cours théorique
    • III-E. Destruction d'une fenêtre
      • III-E-1. Cours théorique
    • III-F. Contexte d'affichage
      • III-F-1. Cours théorique
    • III-G. Gestion de base d'une fenêtre
      • III-G-1. Cours théorique
    • III-H. Interactions avec l'utilisateur
      • III-H-1. Cours théorique
    • III-I. Les timers
      • III-I-1. Cours théorique
    • III-J. Un projet récapitulatif
      • III-J-1. Cours théorique
    • III-K. Affichage de texte dans une fenêtre
      • III-K-1. Cours théorique
    • III-L. Utilisation de polices personnalisées
      • III-L-1. Cours théorique
    • III-M. Affichage d'une image
      • III-M-1. Cours théorique
    • III-N. Dessin
      • III-N-1. Cours théorique
    • III-O. Fond personnalisé
      • III-O-1. Cours théorique
    • III-P. Commandes systèmes
      • III-P-1. Cours théorique
    • III-Q. Menus
      • III-Q-1. Cours théorique
    • III-R. Création dynamique d'un contrôle
      • III-R-1. Cours théorique

III. Les fenêtres

III-A. Fenêtres et boîtes de dialogue

III-A-1. Cours théorique

Les boîtes de dialogue sont semblables aux fenêtres dans le sens où ce sont des fenêtres. Cependant, il faut être bien attentif à marquer la différence qu'il existe entre ces deux types de fenêtres. Les boîtes de dialogue sont gérées en grande partie par le système. Créer une boite de dialogue est relativement simple. Les couleurs de fond, le type de fenêtre ont des valeurs par défaut si l'on utilise un éditeur de ressources.

Dans le cas des fenêtres, il va falloir tout définir lors de l'exécution. Lors de la création d'une fenêtre, il sera nécessaire de préciser un grand nombre de paramètres qui étaient passés implicitement au système grâce aux ressources avec les boîtes de dialogue. Il faut donc bien considérer les boîtes de dialogue comme un cas 'simplifié' de fenêtres.

Cependant, on ne peut pas appliquer toutes les méthodes fonctionnant sur les fenêtres aux boîtes de dialogues ou inversement. Le mode de gestion des boîtes de dialogue est différent de celui des fenêtres, particulièrement au niveau de l'affichage. De manière générale, il faut utiliser les boîtes de dialogue avec des contrôles prédéfinis. Pour créer des fenêtres contenant un affichage personnalisé, des images, des graphiques, du texte utilisant différentes polices, il est préférable d'utiliser une fenêtre. C'est donc pour ce type de travaux que les boîtes de dialogues trouvent leurs limites.

Il faut donc retenir que les fenêtres et les boîtes de dialogue ne fonctionnement pas de la même manière (malgré de nombreuses similitudes). Copier - Coller la procédure d'une fenêtre pour une boîte de dialogue ne serait pas une bonne idée car certains messages sont spécifiques aux fenêtres et d'autres aux boîtes de dialogue.

III-B. Fonctionnement général d'une fenêtre

III-B-1. Cours théorique

Le fonctionnement général des fenêtres a déjà été vu au cours du premier chapitre. Un petit rappel ainsi que quelques précisions est cependant nécessaire.

Lorsqu'une fenêtre est créée, le système lui attribue un identifiant. Cet identifiant est appelé 'handle'. Il identifié la fenêtre dans son ensemble. Toute modification de la fenêtre (déplacement, modification de la taille) se fait grâce à cet identifiant. Une application utilisant plusieurs fenêtres pourra donc être amenée à définir des variables globales contenant les identifiants des différentes fenêtres pour plus de confort. Cette définition globale n'est absolument pas nécessaire et peut être évitée dans le cas d'applications simples (le passage en paramètre des identifiants devient rapidement impossible).

La fenêtre d'une application ne correspond absolument pas à l'application elle même. Une application (ou processus) peut être exécutée et ne pas avoir de fenêtre. La création de la fenêtre est effectuée par le programme lui même. De plus, elle est totalement facultative. Aucune fenêtre ne sera créée par le système au moment de l'exécution de l'application. De plus, la fermeture de la fenêtre n'entraîne pas la fermeture de l'application. C'est au programmeur de faire concorder ces deux événements si il le désire. Bien entendu la majorité des applications créent une fenêtre au démarrage et se terminent lors de la fermeture de cette même fenêtre, mais ce n'est en rien une obligation. De plus, une fenêtre peut avoir plusieurs états, visible, cachée, minimisée... Lorsqu'une fenêtre est créée, elle n'est pas visible. Elle existe, ont peut la déplacer, afficher dans sa zone client... Cependant, rien de tout ceci n'est visible à l'utilisateur. Dès qu'on donne à une fenêtre le statut 'visible', elle apparaît à la même position que lorsqu'elle était invisible. Lorsqu'on détruit une fenêtre, elle n'est plus affichée à l'écran, mais dans ce cas, on ne peut plus la modifier. Elle n'existe plus pour le système. Il ne faut donc pas confondre une fenêtre cachée, qui existe toujours bien que n'étant pas visible, et une fenêtre détruite, qui elle n'existe plus.

Une fenêtre contient différentes zones. La zone client est la zone (généralement blanche) dans laquelle les données de la fenêtre sont affichées. Les menus, la barre de titre, ne font pas partie de la zone client. La zone client est la seule partie de la fenêtre qui soit gérée par l'application. La taille de la fenêtre sur l'écran est donc différente de celle de la zone client. La zone client est nécessairement de taille égale ou inférieure à l'espace occupé par la fenêtre. Pour afficher des données dans une fenêtre, on utilise des coordonnées relatives à la zone client. Pour déplacer une fenêtre, on utilise des coordonnées relatives à l'écran (coin supérieur gauche).

Pour identifier la zone client, on utilise un contexte d'affichage. Il définit l'ensemble des propriétés relatives à l'affichage dans la zone client (nombre de couleurs disponibles, type de police...). Un contexte d'affichage identifie une zone dans laquelle ont peut afficher n'importe quel type de données. Cette zone n'est pas nécessairement affichée à l'écran, on peut par exemple afficher des données dans une fenêtre cachée en utilisant le même contexte d'affichage. Dans ce cas, le contexte d'affichage est lié à la fenêtre, on peut donc l'obtenir à partir de l'identifiant de la fenêtre concernée. Pour afficher dans la zone client, il n'est donc jamais nécessaire de se préoccuper de la position de la fenêtre sur l'écran, il suffit de posséder un 'handle' sur le contexte d'affichage.

III-C. Récupération des messages

III-C-1. Cours théorique

La réception des messages et leur transmission à la procédure de fenêtre appropriée sont essentiels. L'application ne doit jamais cesser 'd'écouter' au cas où de nouveaux messages arriveraient. Si l'application arrête de transmettre les messages, la fenêtre semblera 'bloquée' (affichage persistant, impossibilité de la déplacer…).

La récupération des messages peut se faire grâce à deux fonctions : GetMessage() ou PeekMessage(). La différence entre ces deux fonctions est fondamentale, il ne faut donc pas les assimiler.

GetMessage() est une fonction bloquante. Elle attend qu'au moins un message soit présent dans la file d'attente de la fenêtre pour retourner. Le temps d'attente est infini, tant qu'aucun message n'est placé dans la file d'attente.

PeekMessage() est une fonction non bloquante. Elle vérifie si des messages sont présents et retourne immédiatement. Si au moins un message était présent, elle le retourne, sinon elle ne retourne aucun message. De plus, cette fonction permet de ne pas supprimer les messages de la file d'attente. On peut donc consulter des messages et laisser la boucle de messages principale effectuer le traitement.

L'utilisation de ces deux fonctions s'inscrit dans des cadres totalement différents. En général, GetMessage() est utilisé dans une boucle while. Si aucun message n'est présent, GetMessage() signale au système qu'il peut donner le processeur à une autre application. Placer cette fonction dans une boucle ne génère donc pas d'occupation processeur tant qu'aucun message n'est présent dans la file d'attente. PeekMessage() ne fonctionne pas de cette manière. Placer cette fonction dans une boucle while génère une occupation processeur de 100 %. La fonction sera effectivement appelée un nombre indéfini de fois, occupant ainsi le processeur. La récupération principale des messages doit donc se faire avec la fonction GetMessage().

Cependant, si l'application est occupée, elle peut appeler de manière régulière PeekMessage() suffisamment de fois pour traiter tous les messages. Par exemple, toutes les secondes, PeekMessage() est appelée jusqu'à épuisement de la file d'attente. Le traitement des messages sera plus lent mais il sera effectué quand même. Cette solution évite de devoir faire appel au multi-threading (exécution simultanée de plusieurs parties du programme).

La différence entre ces deux fonctions doit donc être clairement comprise. L'utilisation de l'une ou l'autre de ces fonctions à tort peut s'avérer catastrophique pour les performances du programme et du système dans son ensemble. Dans le cas d'une application n'effectuant pas de tâches lourdes, l'utilisation de PeekMessage() ne sera pas nécessaire. L'interruption du traitement des messages pour une durée courte (jusqu'à un dixième de seconde) est sans conséquences. Il faut tout de même remarquer que GetMessage() ne sera pas rappelée tant que le traitement du message courant n'est pas terminé. Le traitement des messages par la procédure doit donc si possible être bref.

Voici un exemple classique de boucle réalisant le traitement de messages :

Sélectionnez
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
  TranslateMessage(&msg);
  DispatchMessage(&msg);
}

La fonction GetMessage() retourne FALSE si le message WM_QUIT est reçu. Ce message est envoyé à la fenêtre principale pour demander l'arrêt de l'application. Par défaut, ce message n'est pas envoyé lors de la destruction de la fenêtre.

III-D. Création d'une fenêtre

III-D-1. Cours théorique

Avant de pouvoir créer une fenêtre, il faut définir un type. C'est ce type de fenêtre qui sera utilisé pour afficher la fenêtre nouvellement créée. Il est possible d'utiliser des types prédéfinis ou de créer son propre type de fenêtres. Les contrôles utilisés par les boites de dialogues sont des types de fenêtres prédéfinis. Pour définir un nouveau type de fenêtres, on utilise la fonction RegisterClassEx() (cf. chapitre 1).

Une fois le type de fenêtre définit, il faut créer la fenêtre. Pour cela, on utilise la fonction CreateWindowEx() (cf. chapitre 1). On lui passe le type (la classe) de fenêtre désiré ou encore une classe prédéfinie. Lorsque la fenêtre est créée, un message WM_CREATE sera envoyé. C'est à ce moment que l'application doit faire les initialisations qu'elle désire.

La fenêtre créée est initialement cachée. Il faut donc faire un appel à ShowWindow() pour l'afficher. Il est possible de passer à ShowWindow() le paramètre nCmdShow que Windows passe à WinMain(), de cette manière l'état de base de la fenêtre peut être paramétré dans un raccourci.

III-E. Destruction d'une fenêtre

III-E-1. Cours théorique

La destruction d'une fenêtre se traduit par l'envoi à la fenêtre concernée d'un message WM_DESTROY. Il ne faut pas confondre ce message avec le message WM_QUIT. WM_QUIT demande la fermeture totale de l'application, le message WM_DESTROY demande simplement la destruction de la fenêtre en question. Le fait de cliquer sur la 'croix' provoque l'envoi d'un message WM_DESTROY à la fenêtre. Pour faire quitter l'application en même temps que la fermeture de la fenêtre, on peut utiliser la fonction PostQuitMessage() qui elle, envoie un message WM_QUIT. Cette fonction doit être appelée dès la réception du message WM_DESTROY. Le 'handle' de la fenêtre devient invalide dès la destruction de la fenêtre. Pour tester la validité d'un 'handle', on peut utiliser la fonction IsWindow().

Lorsque ce message est reçu, la fenêtre est déjà en cours de destruction. Il est possible de traiter le message WM_CLOSE pour demander une autre action que la destruction de la fenêtre. On peut par exemple, masquer la fenêtre. Une pression sur la 'croix' provoquera donc simplement la disparition à l'écran de la fenêtre, mais pas au niveau système. La fenêtre pourra par la suite être réaffichée dans le même état, par un appel à la fonction ShowWindow().

III-F. Contexte d'affichage

III-F-1. Cours théorique

Un contexte d'affichage (DC) identifie une zone dans laquelle l'application peut afficher tout type de données. La zone identifiée peut être située à n'importe quel endroit de l'écran, elle peut être déplacée, ou même masquée par d'autres fenêtres, sans que l'application en ai conscience. Le fait de déplacer une fenêtre ne change donc rien à la manière dont l'application redessine son contenu. Il en est de même pour une fenêtre cachée ou n'étant pas au premier plan. Toute fenêtre possède un contexte d'affichage associé à sa zone client. Pour obtenir ce contexte, on peut utiliser la fonction GetDC() en spécifiant la fenêtre concernée. Si on spécifie un paramètre NULL, la fonction retourne un contexte d'affichage sur la totalité de l'écran. La zone identifiée couvre l'ensemble de l'écran et est située au-dessus de toutes les fenêtres affichées. Sauf cas très particulier, on n'utilise jamais le contexte d'affichage de la zone écran pour dessiner. Il peut être utilisé lors de la création de bitmaps, etc.

Le contexte d'affichage contient toutes les informations relatives à l'affichage (police, nombre de couleurs)... Il n'identifie pas nécessairement une zone écran. Il peut par exemple identifier une feuille dans le cas d'une impression. On pourra donc utiliser les mêmes fonctions pour afficher à l'écran ou pour envoyer des données imprimables mais en modifiant le contexte d'affichage utilisé.

Lorsqu'on modifie une propriété du contexte d'affichage (ex. : la police courante), on dit qu'on sélectionne un objet dans le contexte. Cet sélection est effectuée grâce à la fonction SelectObject(). Différents types d'objets peuvent être sélectionnés dans un contexte d'affichage (polices, images, pinceaux de dessin…).

III-G. Gestion de base d'une fenêtre

III-G-1. Cours théorique

La gestion d'une fenêtre se fait à deux niveaux : au niveau de la procédure qui gère tous les messages reçus par la fenêtre et par des fonctions permettant diverses manipulation sur la fenêtre.

Voici un rapide aperçu des fonctions les plus utiles pour manipuler les fenêtres :

  • SetWindowText() modifie le texte de la barre de titre.
  • GetWindowText() retourne le texte de la barre de titre.
  • SetWindowPos() modifie la taille de la fenêtre, sa position sur l'écran, ainsi que sa position par rapport aux autres fenêtres (Z-order).
  • GetWindowPlacement() retourne des informations sur le statut courant de la fenêtre spécifiée.
  • GetClientRect() retourne la taille de la zone client de la fenêtre.
  • GetWindowRect() retourne la taille totale occupée par la fenêtre sur l'écran.
  • AdjustWindowRect modifie la taille de la fenêtre spécifiée.
  • GetParent() retourne la fenêtre parent de la fenêtre spécifiée.
  • SetParent() modifie la fenêtre parent de la fenêtre spécifiée.
  • ShowWindow() modifie l'état courant d'une fenêtre.
  • IsIconic(), IsZoomed(), IsWindowVisible() retournent des informations sur l'état de la fenêtre courante.
  • GetWindow() retourne la fenêtre ayant une relation spécifiée dans le Z-order par rapport à la fenêtre passée en paramètre.

La procédure de fenêtre, quant à elle, gère l'ensemble des messages envoyés à une fenêtre. Comme les messages envoyés à une fenêtre sont extrêmement nombreux, il serait très pénible de devoir tous les traiter, en particulier pour des fenêtres simples. L'API Windows fournit la fonction DefWindowProc() qui propose un traitement par défaut de tous les messages envoyés à une fenêtre. Pour un traitement personnalisé du message, il ne faut pas appeler la fonction DefWindowProc(). Le traitement par défaut de la plupart des messages est en général satisfaisant. Cependant, pour imposer des comportements personnalisés à une fenêtre, il s'avère obligatoire d'effectuer un traitement personnalisé de certains messages (ex. : pour masquer la fenêtre lors d'une pression sur la 'croix').

Voici un aperçu rapide des messages les plus courants :

  • WM_CREATE est envoyé à une fenêtre au moment de sa création.
  • WM_PAINT est envoyé lorsqu'une partie de la zone client doit être redessinée.
  • WM_ERASEBKGND est utilisé pour demander l'effacement d'une partie de la zone client.
  • WM_SYSCOMMAND est envoyé pour le traitement de différents événements pouvant survenir (fenêtre minimisée, restaurée, …).
  • WM_ACTIVATE est envoyé lorsqu'une fenêtre est activée ou désactivée.
  • WM_MOVE est envoyé lorsque la fenêtre a été déplacée.
  • WM_SIZE est envoyé lorsque la taille de la fenêtre à été modifiée.
  • WM_CLOSE indique que l'utilisateur demande la fermeture de l'application (en cliquant sur la 'croix' ou en pressant ALT+F4).
  • WM_DESTROY indique que la fenêtre est détruite.

Ces messages sont les premiers à utiliser pour le maniement d'une fenêtre. De nombreux autres seront utilisés pour gérer les interactions avec l'utilisateur (souris, clavier). Ils seront vus plus tard. Dans la plupart des cas, seul un nombre relativement peu élevé de messages sera traité par l'application elle-même. Les autres seront passés à la fonction DefWindowProc().

III-H. Interactions avec l'utilisateur

III-H-1. Cours théorique

Une fenêtre, peut recevoir des données de la part de l'utilisateur de deux manières : par le clavier, ou par la souris. Cependant, la fenêtre ne recevra d'information du système que si elle est active. Si la fenêtre n'est pas active elle ne sera informée ni des événements clavier, ni des événements souris. La fenêtre sera informée de différents événements : pression d'une touche, relâchement d'une touche, mouvement de la souris, pression d'un bouton de la souris… Une fenêtre est informée dès que la souris bouge au-dessus de sa zone client (si elle est active). Le nombre de messages envoyés lorsque la souris se déplace est assez important. Aussi, le traitement de ces messages (WM_MOUSEMOV) doit être bref.

Les messages suivants sont utilisés pour traiter les événements provenant du clavier :

  • WM_KEYDOWN est envoyé lorsqu'une touche est enfoncée. Ce message indique le 'Virtual Key Code' correspondant à la touche.
  • WM_KEYUP est envoyé lorsqu'une touche est relevée. Ce message indique le 'Virtual Key Code' correspondant à la touche.
  • WM_CHAR indique le caractère ASCII correspondant à la touche pressée.
  • WM_SYSKEYUP, WM_SYSKEYDOWN, WM_SYSCHAR sont utilisés pour identifier les événements dus à des touches systèmes.

Remarquons tout de même la différence entre un code ASCII et un 'Virtual Key Code' : le code ASCII identifie un caractère (a,A,E,* ont des codes ASCII différents). Un 'Virtual Key Code' identifie une touche physique, a et A ont donc le même code, mais dans le 2e cas, SHIFT est aussi pressé (en ASCII SHIFT n'a pas de code, ce qui est normal car cette touche ne correspond pas à un caractère).

Les messages suivants sont utilisés pour traiter les événements provenant de la souris :

  • WM_MOUSEMOVE est envoyé à chaque déplacement de la souris (plusieurs dizaines de fois par seconde). Les coordonnées de la souris sont passées en paramètres lors de l'envoi de ce message. Si le message est reçu avec un retard, la position reçue sera celle enregistrée lorsque le message a été envoyé.
  • WM_LBUTTONDOWN est envoyé lorsque le bouton gauche de la souris est pressé.
  • WM_LBUTTONUP est envoyé lorsque le bouton gauche de la souris est relevé.
  • WM_MBUTTONDBLCLK est envoyé lors d'un double clic du bouton gauche de la souris. Ce message n'est envoyé que si le style CS_DBLCLKS est utilisé lors de la création du style de la fenêtre.

Les mêmes messages sont envoyés pour les boutons centraux et droits de la souris. (WM_MBUTTON_ ou WM_RBUTTON_).

Pour obtenir la position courante de la souris, une application peut utiliser la fonction GetCursorPos(). Cette position peut ensuite être convertie grâce aux fonctions ScreenToClient() et ClientToScreen(). Les fonctions GetKeyState() et GetKeyboardState() peuvent être utilisées pour connaître l'état d'une touche ou de l'ensemble du clavier.

III-I. Les timers

III-I-1. Cours théorique

Parfois, il peut être utile d'exécuter des tâches à intervalles fixes. Il existe de nombreux moyens d'effectuer cela, mais une des méthodes les plus simples est d'utiliser les 'timers' fournis par l'API Windows. Les 'timers' ne conviennent qu'à des résolutions de temps faibles, en général pas plus de 10 Hz. Un message est envoyé à intervalles réguliers à une fenêtre spécifiée. L'utilisation des timers pour des fréquences trop importantes peut nuire aux performances générales du système du fait des nombreux messages à envoyer. De plus, le temps de traitement des messages fait qu'il est impossible d'obtenir des intervalles réguliers inférieurs à 50ms. Les timers ne constituent donc qu'une méthode simple pour synchroniser un programme dans des conditions assez restreintes. Cependant, ils conviennent pour la plupart des applications bureautiques.

Les timers sont généralement utilisés avec des fenêtres, cependant, ils peuvent être utilisés sans fenêtres. Dans ce cas, le message sera envoyé au thread qui a demandé la création du timer. La fonction GetMessage() doit donc être appelée avec un argument hWnd égal à NULL de manière à na pas associer la réception des messages à une fenêtre spécifique.

Pour créer un timer, on utilise la fonction SetTimer() en précisant l'intervalle de temps désiré. Un message WM_TIMER sera envoyé à la fenêtre à chaque intervalle de temps. Pour stopper un timer, on utilise la fonction KillTimer(). Comme les timers ont des identifiants, il est possible de créer différents timers avec des intervalles différents. L'identifiant du timer est passé en paramètre lors de la réception du message WM_TIMER.

Voici un exemple utilisant un timer sans fenêtre :

Sélectionnez
MSG msg;
SetTimer(NULL,NULL,60000,NULL);

while(GetMessage(&msg,NULL,0,0))
{
  if(msg.message==WM_TIMER)
    MessageBox(NULL,"Déjà une minute de passé!","Info",MB_OK);
}

III-J. Un projet récapitulatif

III-J-1. Cours théorique

Le projet présenté ici est simple. Il reprend la même création de fenêtre qu'au chapitre 1.2. La différence réside dans le traitement des messages. Tout d'abord, un timer est utilisé pour permettre l'affichage de la fenêtre après deux secondes seulement. La fenêtre est initialement cachée. Le timer est créé lors de la réception du message WM_CREATE. Ce n'est pas une nécessité. Il aurait aussi bien pu être créé après l'appel à CreateWindowEx() mais le fait de créer le timer dans la procédure permet de regrouper l'ensemble des traitements relatifs à la fenêtre dans sa procédure.

Des boîtes de dialogue sont affichées lorsque divers événements sont détectés, pression d'un bouton (gauche ou droit), pression d'une touche. Les clics hors de la zone client ne sont pas pris en charge. Comme la position récupérée par GetCursorPos() est relative à l'écran, il faut la convertir en coordonnées relatives à la zone client. La fonction ScreenToClient() fournie pas l'API Windows est donc la plus appropriée.

La destruction de la fenêtre est suivie de la fermeture de l'application grâce un appel de PostQuitMessage().

Télécharger le projet commenté.

III-K. Affichage de texte dans une fenêtre

III-K-1. Cours théorique

Pour afficher du texte dans la zone client d'une fenêtre, il faut posséder un 'handle' sur le contexte d'affichage de cette fenêtre. Le texte qui sera affiché le sera avec la police courante ainsi que les couleurs de premier plan et de fond courantes. Pour afficher du texte, il existe diverses méthodes. La première est d'utiliser la fonction TextOut(). Cette fonction affiche le texte sur une seule ligne, sans jamais calculer de retour à la ligne. Si le texte dépasse la taille de la fenêtre, il sera tronqué. La fonction DrawText() permet quant à elle de définir un rectangle dans lequel le texte doit être écrit. Si le texte dépasse la largeur, il sera mis à la ligne, et ceci autant de fois qu'il faudra.

La position passée pour dessiner le texte correspond par défaut au coin supérieur gauche de la chaîne de texte qui sera dessinée. Le texte sera donc dessiné vers la droite et vers le bas par rapport au point donné. On peut modifier le point de référence pour le positionnement du texte avec la fonction SetTextAlign(). Pour aligner du texte à droite, on pourra par exemple placer ce point dans le coin supérieur droit. Le texte sera donc dessiné à gauche et en bas de la position passée pour dessiner le texte.

La position passée pour dessiner le texte correspond par défaut au coin supérieur gauche de la chaîne de texte qui sera dessinée. Le texte sera donc dessiné vers la droite et vers le bas par rapport au point donné. On peut modifier le point de référence pour le positionnement du texte avec la fonction SetTextAlign(). Pour aligner du texte à droite, on pourra par exemple placer ce point dans le coin supérieur droit. Le texte sera donc dessiné à gauche et en bas de la position passée pour dessiner le texte.

Pour connaître la taille d'un texte à l'écran (en pixels), il faut utiliser la fonction GetTextExtentPoint32(). Cette fonction retourne la taille qu'occupera un texte donné dans un contexte d'affichage donnée. En effet, la taille du texte dépend du contexte d'affichage car la modification de la police entraîne une modification de la taille occupée par le texte. L'utilisation de polices personnalisées sera vue dans le chapitre suivant.

La fonction suivante dessine un texte en rouge centré dans la zone client de la fenêtre spécifiée.

Sélectionnez
void DrawCenteredText(HWND hWnd,char *text)
{
  HDC hDC;
  RECT rcClient;
  GetClientRect(hWnd,&rcClient);
  hDC=GetDC(hWnd);
  SetTextColor(hDC,0x000000FF);
  SetBkMode(hDC,TRANSPARENT);
  SetTextAlign(hDC,TA_CENTER|TA_TOP);
  TextOut(hDC,(int)((float)rcClient.right/2),5,text,strlen(text));
  ReleaseDC(hWnd,hDC);
}

III-L. Utilisation de polices personnalisées

III-L-1. Cours théorique

Pour utiliser une police personnalisée pour l'affichage du texte, il faut tout d'abord créer la police spécifiée. Pour cela, on réalise un appel à CreateFont() pour déterminer la police voulue, la taille, le style… Il faut ensuite sélectionner la police dans le contexte d'affichage avec la fonction SelectObject(). Une fois qu'une police est sélectionnée dans un contexte d'affichage, chaque texte affiché le sera avec cette police.

SelectObject() retourne le 'handle' de la police précédemment sélectionnée. Une fois l'utilisation de la police terminée, l'application doit resélectionner l'ancienne police par un nouvel appel à SelectObject(). Ensuite, elle doit détruire la police inutilisée par un appel à DeleteObject().

La suppression des objets créés par une application est faite automatiquement lorsque celle-ci se termine. Cependant, ils occupent de la place inutilement durant l'exécution de l'application. Il est donc important de supprimer les objets inutilisés. De plus, le nombre d'objets disponible pour une application donnée est limité, si l'application recrée une nouvelle police à chaque modification de l'affichage sans supprimer l'ancienne, elle va rapidement atteindre le nombre maximal d'objets.

Voici un exemple d'appel à CreateFont() :

Sélectionnez
HFONT Police;
Police=CreateFont(
  20,
  0,
  0,
  0,
  700,
  FALSE,
  FALSE,
  FALSE,
  0,
  OUT_DEFAULT_PRECIS,
  CLIP_DEFAULT_PRECIS,
  DEFAULT_QUALITY,
  DEFAULT_PITCH | FF_DONTCARE,
  "Comic Sans MS"
  );

III-M. Affichage d'une image

III-M-1. Cours théorique

Avant de pouvoir afficher une image, il faut tout d'abord la charger. Nous ne traiterons ici que les images bitmap. En effet, ce sont les seules images prises en charge par l'API Windows. Pour effectuer des traitements sur les images avec des fonctions du GDI (Graphic Device Interface), il faut donc tout d'abord les convertir au format bitmap. Les images sont considérées comme des objets du GDI, au même titre que les polices. On peut donc utiliser les fonctions de manipulation d'objet (ex. : SelectObject() ou DeleteObject()) aussi bien sur des images que sur des polices.

On peut charger une image à partir du disque ou encore à partir des ressources du programme. Pour cela, on peut utiliser deux fonctions : LoadBitmap() ou LoadImage(). La fonction LoadBitmap() est beaucoup moins complète que LoadImage(), il est donc préférable d'utiliser cette dernière. Ce n'est cependant pas une obligation. Une fois l'image chargée en mémoire, le GDI retourne un 'handle' (de type HBITMAP) sur l'image. Elle peut alors être affichée.

Le résultat de l'affichage dépend bien entendu du contexte d'affichage utilisé (en particulier du nombre de couleurs qu'il comprend). Pour afficher l'image, on utilisera la fonction DrawState(). Cette fonction n'affiche pas nécessairement des bitmaps, elle peut aussi afficher des icônes par exemple. De plus, cette fonction est capable de modifier la taille d'affichage du bitmap. Il n'est cependant pas recommandé d'agrandir la taille des images à l'affichage car l'agrandissement est alors de très mauvaise qualité.

Voici un exemple de fonction dessinant une image chargée depuis un fichier dans la fenêtre spécifiée :

Sélectionnez
void PrintBmp(HWND hWnd, char *filename)
{
  HBITMAP hBmp;
  HDC hDC;
  hBmp=(HBITMAP)LoadImage(NULL,filename,IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
  hDC=GetDC(hWnd);
  DrawState(hDC,NULL,NULL,(LPARAM)hBmp,NULL,0,0,0,0,DST_BITMAP);
  DeleteObject(hBmp);
  ReleaseDC(hWnd,hDC);
}

III-N. Dessin

III-N-1. Cours théorique

L'affichage de 'dessins' (lignes, cercles, points) dans une fenêtre passe par le GDI. Les dessins se font avec un pointeur. Par défaut, le pointeur est un pixel unique. Tracer une ligne donne donc le résultat attendu : une ligne de 1 pixel de large. Pour dessiner une ligne plus large, il va falloir remplacer le pointeur courant par un pointeur plus large. Pour cela, on utilise le fonction CreatePen(). Elle retourne une variable de type HPEN. Ensuite, il faut sélectionner le pointeur ainsi créé dans le contexte d'affichage courant. Bien entendu, il faudra, tout comme avec les polices, supprimer les pointeurs dès que ceux-ci ne sont plus utilisés.

Voici un aperçu des fonctions utilisées pour dessiner :

  • SetPixel() dessine un pixel unique, indépendamment du pointeur courant.
  • GetPixel() retourne la couleur du pixel à la position spécifiée.
  • MoveToEx() déplace la position courante du curseur de dessin.
  • LineTo() trace une ligne depuis la position courante du curseur de dessin jusqu'à la position spécifiée. La ligne est tracée avec le pointeur courant.
  • Arc(), ArcTo() et AngleArc() dessinent des ellipses ou des portions d'ellipses.
  • Polyline() dessine une série de lignes connectées.

III-O. Fond personnalisé

III-O-1. Cours théorique

Jusqu'à présent, nous avions laissé Windows effacer le fond. L'effacement de la zone client avant le redessinement n'est pas nécessaire, la méthode la plus simple et la plus rapide est de redessiner par-dessus l'ancienne zone. Toutefois, si l'on veut redessiner une partie de la zone client seulement ou si le nouveau dessin ne couvre pas toute la zone (du texte avec fond transparent par exemple) il est nécessaire d'effacer d'abord.

Pour cela, il faut utiliser le message WM_ERASEBKGND. Lorsque ce message est envoyé, un 'handle' sur le contexte d'affichage à effacer est passé en paramètre. Il faut impérativement utiliser le 'handle' passé en paramètre pour effectuer toutes les opérations de dessin. L'application dispose de plusieurs solutions pour effacer la zone client. Elle peut effectuer des tâches complexes, afficher des images, puis un texte… Si les images ne couvrent pas toutes la zone, il faut effacer les parties qui resteront visibles. Pour cela, on peut utiliser la fonction FillRect(). Cette fonction ne prend pas en paramètre une couleur mais un HBRUSH. On peut créer des 'brushes' à partir d'images bitmap avec CreatePatternBrush() ou encore à partir d'une couleur unie avec CreateSolidBrush(). Si l'application désire simplement modifier la couleur de fond de sa zone client, elle n'est pas obligée de traiter le message WM_ERASEBKGND. Elle peut simplement utiliser CreateSolidBrush() et passer le résultat au membre hbrBackground de la structure WNDCLASSEX lors de la création de la classe de la fenêtre. De cette manière la fonction DefWindowProc() écrasera le fond elle-même avec la couleur demandée.

III-P. Commandes systèmes

III-P-1. Cours théorique

Pour personnaliser une application, on peut être amené à modifier le traitement standard des commandes systèmes. Pour cela, on utilise le message WM_SYSCOMMAND. Ce message est envoyé en diverses occasions : déplacement de la fenêtre, utilisation du menu de contrôle sur la barre de titre (fermer, minimiser, restaurer), activation de l'écran de veille, déroulement du menu démarrer… En interceptant ce message, une application peut définir des réactions personnalisées aux traitements standard Windows. Par exemple, masquer la fenêtre au lieu de la détruire lors d'un clic sur la 'croix'. Comme le même message est envoyé (WM_SYSCOMMAND) quel que soit la commande système, il faut impérativement appeler DefWindowProc() pour traiter les commandes systèmes non prises en charge par l'application.

III-Q. Menus

III-Q-1. Cours théorique

Il existe deux manières d'insérer des menus dans une application. La première méthode est de créer un menu grâce à un éditeur de ressources puis de l'insérer dans le programme. La seconde méthode est dynamique, grâce aux fonctionnalités de l'API Windows. De manière générale, les menus obtenus par des ressources seront utilisés pour les menus principaux des fenêtres, pour créer des barres de menus. Pour les menus contextuels, boîtes à outils, …, il est préférable d'utiliser des menus dynamiques. A chaque clic dans un des menus, la fenêtre possédant le menu reçoit un message WM_COMMAND indiquant l'identifiant de l'option choisie.

Pour obtenir une barre de menus à partir d'un fichier de ressources, il suffit de créer le menu dans l'éditeur de ressources puis de passer son identifiant au membre lpszMenuName de la structure WNDCLASSEX. Le menu sera inséré automatiquement dans la fenêtre.

Pour un menu dynamique, on utilisera l'API Windows. Les menus sont identifiés par une variable HMENU. Il faut tout d'abord créer le menu. Il sera initialement vide. Pour cela, il faut utiliser la fonction CreatePopupMenu(). Une fois le menu créé, on peut lui ajouter des éléments (options, sous menus, séparateurs) avec les fonctions AppendMenu() ou InsertMenuItem(). Une fois le menu créé, il faut le dessiner. Pour cela, on utilise les fonctions TrackPopupMenu() ou TrackPopupMenuEx(). Pour une boîte à outils, il suffira de dessiner le menu à la position courante obtenue par un appel à GetCursorPos(). Si le menu est associé à une fenêtre, il sera supprimé automatiquement lors de la destruction de la fenêtre. Dans le cas contraire, l'application doit appeler la fonction DestroyMenu() pour libérer les ressources.

La fonction suivante dessine un menu contextuel :

Sélectionnez
void PrintMenu(HWND hWnd)
{
  HMENU hMenu;
  POINT pt;
  GetCursorPos(&pt);
  hMenu=CreatePopupMenu();
  AppendMenu(hMenu,MF_STRING,1,"Item 1");
  AppendMenu(hMenu,MF_STRING,2,"Item 2");
  AppendMenu(hMenu,MF_SEPARATOR,NULL,NULL);
  AppendMenu(hMenu,MF_STRING,3,"Item 3");
  TrackPopupMenu(hMenu,NULL,pt.x,pt.y,0,hWnd,NULL);
}

Dans cet exemple, les valeurs 1, 2 et 3 passées à AppendMenu() représentent les identifiants des options. Ces valeurs seront passées en paramètre lors de l'envoi du message WM_COMMAND. Elles n'ont aucune signification particulière, si ce n'est pour le programmeur lui-même.

III-R. Création dynamique d'un contrôle

III-R-1. Cours théorique

La création dynamique de contrôles ne présente aucune difficulté particulière. Elle se fait grâce à la fonction CreateWindowEx(), en utilisant des classes de fenêtres prédéfinies. Une fois le contrôle créé, il faut tout de même remarquer que son utilisation est quelque peu différente. En effet, des fonctions telles que SetDlgItemText() ne fonctionneront plus. Il faudra utiliser la fonction SetWindowText() à la place, puisque le contrôle est une fenêtre. La fonction SetDlgItemText() n'est en fait qu'un raccourci qui appelle SetWindowText() après avoir utilisé GetDlgItem() pour obtenir le 'handle' du contrôle correspondant.

Les styles utilisés dans l'éditeur de ressources sont les mêmes. La fonction CreateWindowEx() acceptera donc comme styles l'ensemble des styles utilisés dans l'éditeur de ressources.

Les classes suivantes sont prédéfinies : BUTTON, COMBOBOX, EDIT, LISTBOX, MDICLIENT, RichEdit, RICHEDIT_CLASS, SCROLLBAR, STATIC.


Programmation avec l'API Windows

Table des matières

  • IV. Le système de fichier
    • IV-A. Introduction
      • IV-A-1. Cours théorique
    • IV-B. Création et ouverture d'un fichier
      • IV-B-1. Cours théorique
    • IV-C. Lecture/Écriture dans un fichier
      • IV-C-1. Cours théorique
    • IV-D. Manipulations sur les fichiers
      • IV-D-1. Cours théorique
    • IV-E. Enumération de fichiers
      • IV-E-1. Cours théorique
    • IV-F. Manipulations sur les dossiers
      • IV-F-1. Cours théorique
    • IV-G. Manipulations des chemins
      • IV-G-1. Cours théorique

IV. Le système de fichier

IV-A. Introduction

IV-A-1. Cours théorique

L'API Windows fournit une suite de fonctions permettant d'accéder au système de fichier. Bien entendu, il est toujours possible d'utiliser les fonctions classiques du C (fopen(), fprintf()) pour accéder au système de fichier. Cependant, ces fonctions ne supportent pas toutes les possibilités du système de fichier Windows (ex. : la gestion du partage des ressources dans l'environnement Windows). Il est donc en général préférable d'utiliser les fonctions fournies avec l'API Windows pour manipuler le système de fichier.

Les fonctions de manipulation de fichiers (ouverture, écriture, lecture) seront donc abordées dans ce paragraphe. De plus, l'utilisation de fichiers dans un contexte multitâches impose des précautions d'emploi supplémentaires. Le non respect de certaines précautions simples peut entraîner des erreurs ou même la perte ou l'écrasement de données.

Le problème de la recherche de fichiers dans le système de fichier sera également abordé.

Ce paragraphe a donc pour but de présenter de manière générale les opérations de base lors de la manipulation du système de fichier Windows grâce aux fonctions de l'API Windows.

IV-B. Création et ouverture d'un fichier

IV-B-1. Cours théorique

L'API Windows fournit la fonction CreateFile() pour ouvrir ou créer des fichiers. Cette fonction permet de gérer le partage du fichier au niveau système, les attributs de ce fichier. Il est important de bien comprendre les paramètres principaux de cette fonction.

Avant la description plus en détail de cette fonction, il est important de bien comprendre le fonctionnement du système de fichiers sous Windows. Comme cet environnement est multitâches, il est possible qu'un fichier soit accédé en même temps par plusieurs programmes ou même par le même programme. Le premier programme à demander l'accès au fichier doit préciser s'il souhaite ou non le partager. Si le fichier n'est pas partagé, le système refusera tout autre appel à CreateFile() pour accéder à ce fichier. Le fichier ne sera donc plus accessible au reste du système. Le fichier restera 'réservé' jusqu'à ce que le programme se termine ou jusqu'à ce que la fonction CloseHandle() soit appelée (cette dernière fonction sera expliquée plus tard). En ne partageant pas le fichier le programme s'assure qu'il est le seul à accéder au fichier.

Un fichier peut également être partagé en lecture. Dans ce cas, seul le programme ayant accédé au fichier en premier aura un accès en lecture/écriture. Les appels suivants à CreateFile() permettront seulement d'accéder au fichier en lecture.

Si le fichier est partagé en lecture/écriture, alors l'accès simultané en lecture ou écriture à ce fichier est autorisé. Plusieurs programmes peuvent donc lire ou écrire des données simultanément dans ce fichier. Ce dernier mode de partage est très dangereux et ne doit être utilisé qu'en connaissance de cause.

Lors du premier appel à CreateFile(), le mode de partage du fichier est défini. Il ne pourra pas être modifié par un autre appel. Même si le fichier est partagé, son mode de partage ne pourra pas être modifié. Si l'appel à la fonction CreateFile() réussit, le système retourne un HANDLE au programme. Ce HANDLE identifie le fichier. La fonction CloseHandle() permet de refermer le fichier. À partir de cet appel, le HANDLE identifiant le fichier n'est plus valide.

L'identifiant du fichier stocke également la position courante du pointeur. Le système stockera donc la position courante d'écriture pour chaque nouvel appel à CreateFile(). Si le fichier est ouvert 10 fois simultanément, le système stockera 10 positions. Le partage du fichier n'entraîne donc aucune modification dans les méthodes de lecture/écriture sur le fichier (le pointeur courant ne sera pas modifié même si un autre programme accède le fichier). Cependant, si deux threads utilisent le même HANDLE pour accéder à un fichier, alors le pointeur pourra être modifié par l'un ou par l'autre. Si le même thread fait deux appels successifs à l'API pour lire le fichier, rien ne garantit qu'il lira deux blocs successifs. En effet, si le deuxième thread a modifié le pointeur entre les deux appels, alors la lecture se poursuivra à la nouvelle position.

Pour éviter des complications, le plus simple est généralement de ne pas partager les fichiers ou d'autoriser seulement le partage en lecture.

Voici un détail de principaux arguments de la fonction CreateFile() :

Sélectionnez
HANDLE CreateFile(
  LPCTSTR lpFileName,          // pointer to name of the file
  DWORD dwDesiredAccess,       // access (read-write) mode
  DWORD dwShareMode,           // share mode
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
                               // pointer to security attributes
  DWORD dwCreationDisposition,  // how to create
  DWORD dwFlagsAndAttributes,  // file attributes
  HANDLE hTemplateFile         // handle to file with attributes to
                               // copy
);
  • lpFileName est le nom du fichier à ouvrir ou à créer.
  • DwDesiredAccess est le type d'accès demandé : lecture (GENERIC_READ), écriture (GENERIC_WRITE) ou les deux. Si le fichier est partagé en lecture seulement et qu'un accès en écriture est demandé, la fonction retournera une erreur.
  • DwShareMode indique le mode de partage demandé. Ce mode peut être non partagé (NULL), partagé en lecture (FILE_SHARE_READ), partagé en écriture (FILE_SHARE_WRITE) ou les deux.
  • DwCreationDisposition indique la méthode utilisée pour lire ou créer le fichier. CREATE_NEW demande la création d'un fichier. Si le fichier existe déjà, la fonction retournera une erreur. CREATE_ALWAYS demande la création d'un fichier. Si le fichier existe déjà, il est écrasé. OPEN_EXISTING demande l'ouverture d'un fichier existant. Si le fichier n'existe pas, le fonction retourne une erreur. PEN_ALWAYS demande l'ouverture du fichier. Si le fichier n'existe pas il est créé.
  • dwFlagsAndAttributes indique les attributs utilisés pour créer le fichier.

La fonction retournera un HANDLE en cas de succès ou INVALID_HANDLE_VALUE, c'est à dire le pointeur 0xFFFFFFFF est cas d'erreur.

Dès que le fichier n'est plus utilisé, l'application peut utiliser CloseHandle() pour signifier qu'elle n'utilise plus le fichier spécifié. Toute autre application pourra alors avoir accès à ce fichier si elle le demande.

IV-C. Lecture/Écriture dans un fichier

IV-C-1. Cours théorique

Avant de pouvoir lire ou écrire dans un fichier, il faut réaliser un appel à CreateFile(). Le HANDLE retourné permettra d'identifier le fichier dans lequel on souhaite effectuer les opérations. La fonction ReadFile() permet la lecture dans un fichier. Elle déplace le pointeur courant à la nouvelle position. ReadFile() lit le nombre d'octets spécifiés et retourne un indicateur booléen, ainsi que le nombre d'octets lus. ReadFile() retournera toujours TRUE si le pointeur sur le fichier est valide. Pour détecter la fin du fichier, il faut comparer le nombre d'octets lus au nombre d'octets demandés. Si 256 octets sont demandés à la lecture et que ReadFile() indique que seulement 18 octets ont été lus, alors la fin du ficher est atteinte.

La fonction WriteFile() a un mode de fonctionnement similaire à ReadFile(). Elle écrit le nombre d'octets demandé, retourne un indicateur de succès ainsi que le nombre d'octets effectivement écrits.

Pour déplacer le pointeur courant on utilise la fonction SetFilePointeur(). La nouvelle position peut être spécifiée à partir du début du fichier, de la position courante du fichier ou de la fin du fichier. Cette fonction retourne la nouvelle position. Pour obtenir la position courante du pointeur, on appelle SetFilePointeur() en demandant un déplacement nul à partir de la position courant. On récupère ainsi la position courante du pointeur.

La fonction SetEndOfFile() détermine la position de la fin du fichier. La fin du fichier est alors placée à la position courante du pointeur.

IV-D. Manipulations sur les fichiers

IV-D-1. Cours théorique

D'autres fonctions peuvent être utilisées lors de la manipulation de fichiers. Pour obtenir la taille d'un fichier, il faut utiliser GetFileSize(). Cette fonction retourne sur 32 ou 64 bits la taille du fichier spécifié.

Les fonctions MoveFileEx(), CopyFileEx() et DeleteFile() peuvent être utilisées pour les manipulations courantes sur les fichiers. Ces fonctions ne nécessitent pas que le fichier soit précédemment ouvert par CreateFile(). La fonction DeleteFile() supprime un fichier sans le faire passer par la corbeille. Il est donc impossible d'annuler la suppression du fichier. Pour envoyer simplement un fichier dans la corbeille, il faut utiliser la fonction SHFileOperation(). Cette fonction permet à la fois la suppression et l'envoi à la corbeille d'un fichier.

Le système de fichier VFAT (utilisé sous Windows 95/98) manipule des noms courts et des noms longs. Les noms de fichiers courts (format 8.3) sont un héritage des précédents systèmes de fichiers utilisés. Pour convertir des noms de fichiers en version courte ou longue on utilise les fonctions GetShortPathName() et GetFullPathName().

IV-E. Enumération de fichiers

IV-E-1. Cours théorique

Pour énumérer des fichiers, l'API Windows offre une sélection de trois fonctions : FindFirstFile(), FindNextFile() et FindClose(). La fonction FindFirstFile() initialise la recherche et retourne le premier fichier trouvé (s'il y en a un). Elle retourne un HANDLE sur la recherche en cours, pour permettre de poursuivre la recherche. Le fonction FindNextFile() doit ensuite être appelée pour récupérer l'ensemble des fichiers trouvés. Généralement cette fonction sera appelée dans une boucle. La fonction FindClose() referme le HANDLE et libère la mémoire occupée.

Cette suite de fonctions permet d'effectuer des recherches dans un dossier. Attention, les sous dossiers ne seront pas parcourus. La recherche peut porter sur un listage non exhaustif, par exemple, listage des fichiers *.txt. Pour un listage exhaustif, il faudra demander le listage des fichiers *.*. Le type de recherche, ainsi que le dossier de recherche sont passés à la fonction FindFirstFile() sous forme d'une chaîne de caractère de type : Chemin\\Masque (ex. c:\*.*). Aucune fonction n'est fournie pour le parcours récursif des dossiers de manière à explorer les dossiers et sous dossiers. Pour effectuer une recherche complète, il faut effectuer des appels successifs à ces trois fonctions de manière à parcourir l'ensemble des sous dossiers. Lors d'une recherche, les pseudo-dossiers '.' et '..' sont également listés. Il peut donc être utile d'effectuer un test pour supprimer ces dossiers du listage final. Les informations concernant les fichiers et dossiers listés sont placées dans une structure WIN32_FIND_DATA (attributs, nom, taille…).

IV-F. Manipulations sur les dossiers

IV-F-1. Cours théorique

L'API Windows offre peu de manipulations possibles sur les dossiers. La fonction CreateDirectory() permet la création d'un dossier. Attention toutefois au fait que cette fonction ne crée pas les sous dossiers nécessaires. Si la création du dossier demandé demande la création implicite de plusieurs dossiers, la fonction retournera une erreur. Par exemple, le dossier c:\a\b ne sera créé que si le dossier c:\a existait déjà. Pour créer ce dossier, il faudra faire deux appels à CreateDirectory() pour créer respectivement les dossiers c:\a et c:\a\b.

Pour supprimer un dossier, on utilisera la fonction RemoveDirectory(). Cette fonction ne supprimera le dossier que si il est vide. Pour supprimer un dossier non vide il faudra donc lister l'ensemble des fichiers et dossiers et les supprimer avant de supprimer le dossier parent.

La fonction GetCurrentDirectory() retourne le répertoire courant. Elle peut être utilisée par exemple pour obtenir le chemin du processus courant. En effet, généralement, les programmes sont exécutés avec pour dossier courant, le dossier contenant l'exécutable. Ceci est toujours vrai sauf mention contraire de l'utilisateur. La récupération du chemin de l'exécutable courant par cette méthode n'est donc pas garantie mais elle marchera dans la plupart des cas.

La fonction GetTempPath() permet de récupérer le chemin du dossier temporaire (généralement Windows\temp). Les fonctions GetWindowsDirectory() et GetSystemDirectory() permettent de récupérer le chemin du dossier contenant Windows et du dossier système (system ou system32).

IV-G. Manipulations des chemins

IV-G-1. Cours théorique

Windows fournit une collection de fonctions permettant de travailler sur les fichiers, les dossiers ou les chemins. Voici une liste non exhaustive des fonctions les plus utilisées :

  • PathIsDirectory() permet de déterminer si le chemin spécifié est un dossier.
  • PathFileExist() détermine si le fichier spécifié existe.
  • PathCanonicalize() supprimer toutes les références symboliques du chemin et le convertit en chemin absolu. Ex. : (c:\a\.. sera converti en c:\).
  • PathCompactPath() permet de réduire la taille occupée par le chemin. Cette fonction remplace une partie de chemin par des points.
  • PathFindExtension() retourne l'extension du nom de fichier spécifié.
  • PathFindFileName() retourne le nom de fichier dans le chemin spécifié.
  • PathGetArgs() retourne les arguments du chemin spécifié.
  • PathRelativePathTo() permet de relativiser un chemin par rapport à un autre chemin.
  • PathRemoveArgs() supprime les arguments du chemin passé en paramètre.
  • PathRemoveExtension() supprime l'extension du chemin, si celle-ci existe.

Pour une liste exhaustive, reportez vous à l'annexe B.


Programmation avec l'API Windows

Table des matières

  • V. Le multithreading
    • V-A. Introduction
      • V-A-1. Cours théorique
    • V-B. Notion de processus et de thread
      • V-B-1. Cours théorique
    • V-C. Partage des tâches
      • V-C-1. Cours théorique
    • V-D. Synchronisation
      • V-D-1. Cours théorique
    • V-E. Premier exemple
      • V-E-1. Cours théorique
    • V-F. Arrêt d'un thread
      • V-F-1. Cours théorique
    • V-G. Récupération des messages
      • V-G-1. Cours théorique
    • V-H. Communication inter-threads
      • V-H-1. Cours théorique
    • V-I. Sections Critiques
      • V-I-1. Cours théorique
    • V-J. Fonctions d'attente
      • V-J-1. Cours théorique
    • V-K. Événements
      • V-K-1. Cours théorique
    • V-L. Sémaphores
      • V-L-1. Cours théorique
    • V-M. Trois projets simples
      • V-M-1. Cours théorique
    • V-N. Performances
      • V-N-1. Cours théorique

V. Le multithreading

V-A. Introduction

V-A-1. Cours théorique

Commençons tout d'abord par une explication sur le terme de multithreading. Le multithreading est le fait d'exécuter des tâches simultanément (multitâche). Les termes multitâche et multithread ne sont pas totalement similaires. Le terme multithread indique une application qui effectue différentes taches simultanément. Le terme de multitâche est un terme plus général et plus commun pour parler d'un système capable de gérer plusieurs applications simultanément.

Dans un programme classique, les instructions sont traitées de manière linéaire, "ligne à ligne". En ignorant le reste du système on pourrait dire que le processeur exécute les instructions de la ligne courante, puis passe à la suivante. En réalité ce n'est pas tout à fait vrai car les autres applications doivent elles aussi avoir accès au processeur, ainsi que le système d'exploitation lui-même.

Un thread est en fait une unité d'exécution. Chaque thread peut avoir accès au processeur et exécuter des instructions. Un thread peut se comporter exactement comme si il ne partageait pas le processeur (mais dans ce cas, il faudrait disposer d'autant de processeurs qu'il y a de threads). Pour éviter cet inconvénient, le système donne l'accès au processeur à chaque thread durant un temps très court (quelques millisecondes), et ceci de manière circulaire. Si le nombre de thread augmente, chaque thread devra attendre un délai plus long avant d'obtenir de nouveau l'accès au processeur.

De cette manière, chaque application peut fonctionner comme si elle était seule à utiliser le processeur. La seule différence est que l'exécution est plus lente. Si le processeur est suffisamment rapide, l'exécution d'un nombre modéré de threads peut s'effectuer très rapidement. C'est le cas actuellement. Dans un système utilisant Windows, le nombre de threads avoisine 50 au repos. Il dépasse rapidement 100.

L'utilisation du multithread consiste à créer un programme qui comporte plusieurs threads. Ce programme peut donc exécuter plusieurs instructions "simultanément". On peut alors se demander l'utilité d'une telle pratique. En effet, puisque le processeur est partagé, l'exécution du programme ne sera pas plus rapide. Elle sera seulement fragmentée. Un exemple simple permet de comprendre l'utilité du multithreading.

Considérons une application réalisant un transfert de fichier. Cette application dispose d'une interface graphique présentant une barre de progression ainsi qu'un bouton 'annuler'. En effet l'utilisateur désire être tenu au courant de l'avancement de la copie. De plus il veut pouvoir stopper celle-ci à tout moment si il le désire. Le programme doit donc réaliser une boucle de manière à recevoir les messages. Mais il doit également s'occuper de la copie des fichiers, ce qui reste son rôle principal. Si le programme écrit sur le disque, il ne peut plus recevoir les messages. La fenêtre ne sera donc plus rafraîchie. De plus, si l'utilisateur clique 'annuler', l'application ne traitera pas cette demande puisqu'elle ne recevra même pas le message… Une telle application utilisera donc 2 threads. Le premier thread s'occupera de la réception et du traitement des messages. Le deuxième thread s'occupera de la copie des fichiers. De cette manière, la réception des messages sera effectuée même au cours de la copie. Si l'utilisateur demande l'arrêt de la copie, le premier thread devra simplement stopper l'exécution du second. La copie sera alors stoppée.

V-B. Notion de processus et de thread

V-B-1. Cours théorique

Il ne faut pas confondre les processus et les threads. Un processus est une application. Tout processus dispose d'au moins un thread. Il peut cependant en posséder plusieurs. Pour l'utilisateur moyen, les threads sont invisibles. Ils sont masqués par les processus. La différence entre un processus et un thread est qu'un processus ne constitue en rien une unité d'exécution. L'exécution simultanée de plusieurs processus est réalisée grâce au multithreading. Le processus représente simplement un application vis-à-vis du système. Lorsque l'utilisateur demande la terminaison d'une application, tous les threads appartenant à cette application devront être terminés.

Tout processus doit comporter au moins un thread, de manière à exécuter le point d'entrée du programme. Ce thread ne pourra jamais être terminé sans que l'application ne soit elle-même terminée. Le processus ne doit en aucun cas être assimilé à ce thread. Le thread constitue simplement l'unité d'exécution de base du processus. Le système ne communiquera jamais avec le processus mais toujours avec l'un des threads de ce processus. En effet, le processus n'étant pas une unité d'exécution il ne réalise aucune tâche. Le premier thread est créé par le système. C'est pour cette raison que dans un programme comportant un seul thread, aucune référence n'est faite aux fonctions de l'API relatives aux threads. La création de nouveaux threads devra être explicite. C'est ce qui sera étudié tout au long de ce chapitre.

V-C. Partage des tâches

V-C-1. Cours théorique

Avant d'utiliser des threads, il est nécessaire de bien comprendre comment le système gère ces threads. En effet, un programme multithread mal conçu peut conduire à une très forte baisse des performances système.

Il faut bien comprendre que si le système d'exploitation accorde à chaque thread un temps d'accès au processeur, le thread n'est pas obligé d'utiliser totalement ce temps. Le système accorde simplement un temps maximum d'utilisation. Au-delà de cette période, le contrôle du processeur sera passé à un autre thread. Si le thread n'a plus aucune tâche à effectuer, il doit repasser lui-même le contrôle au système. De cette manière, une application inactive ne monopolisera pas inutilement le processeur. Si une application inactive n'effectue pas ce passage, elle utilisera totalement son quota d'accès. Le système affichera alors une charge processeur de 100% et les performances seront fortement ralenties.

Le système d'exploitation dispose également d'un système de priorités permettant de gérer les quotas d'accès alloués aux processus. Voici un résumé rapide du fonctionnement de ce système de priorité :

  • Time Critical : ce thread requiert l'utilisation totale du processeur. Les quotas alloués à ce thread sont infinis. Si un tel thread ne repasse pas spontanément le contrôle au reste du système, le système restera en attente. Le contrôle du processeur ne sera accordé à aucun autre thread. Tout le système sera alors bloqué jusqu'à ce que le thread repasse le contrôle. Ce type d'application n'est en réalité quasi jamais utilisé. Il doit être réservé à des utilisations très particulières (ex : un gestionnaire de souris).
  • High : ce thread recevra le contrôle du processeur en priorité. Un tel thread ne peut pas être bloquant pour le système mais il pourra ralentir énormément les performances. Un thread utilisant cette priorité et effectuant un accès permanent au processeur causera un effondrement des performances générales du système. Cette priorité ne doit généralement être accordé qu'à des applications effectuant des tâches très brèves ou étant en premier plan. Les performances de ce thread ne seront quasiment pas affectées par l'exécution d'autres applications.
  • Normal : ce thread a une priorité normale. Il recevra le processeur exactement autant de fois que les autres threads de la même priorité. Si deux threads utilisent cette priorité et utilisent le processeur en permanence, les performances de chacun des thread seront divisées par 2. La quasi totalité des threads utilise cette priorité (c'est d'ailleurs la plus recommandée hors cas très particuliers).
  • Idle : ce thread recevra le contrôle du processeur seulement si aucune autre application ne demande le contrôle. Si ce thread effectue des tâches en continu et que toutes les autres applications sont inactives, il disposera de l'accès processeur dans sa quasi totalité. Si un autre thread effectue des tâches continues, ce thread n'aura plus aucun accès au processeur. Ce type de priorité peut être accordé à des applications effectuant des tâches de fond. De cette manière les performances du système resteront inchangées. La charge du processeur sera alors continuellement de 100 %. Le partage sera alors : 0 % pour le thread Idle si le système utilise le processeur. 100 % pour le thread Idle si le système est inactif.

De manière générale il est inutile de modifier la priorité des threads. La modification des priorités ne doit être faite que dans les cas particuliers et en connaissance de cause. En particulier, les priorités Time Critical et High ne doivent jamais être utilisées abusivement.

De manière à conserver des performances système optimales, un thread doit repasser le contrôle au système dès qu'il devient inactif. Pour cela il dispose de plusieurs méthodes. Prenons l'exemple d'un thread qui désire attendre 150ms. Si ce thread effectue une boucle en testant l'heure système, il utilisera totalement son quota et le système affichera une charge de 100 %. Si eu lieu de cela le thread effectue un appel à la fonction Sleep(), il repassera le contrôle au reste du système pour une durée de 150ms. Le contrôle du processeur ne lui sera plus accordé durant cette durée. La charge processeur sera alors de 0 %. Le thread sera alors en attente. De cette manière les autres threads auront accès au processeur comme ci ce thread n'était pas présent et les performances système seront conservées.

Un autre exemple est la récupération des messages. La fonction GetMessage() repasse le contrôle au système dès que tous les messages ont été traités. Si l'application est inactive, le contrôle est donc repassé immédiatement au système et la charge processeur affichée est de 0 %. Si un thread utilise PeekMessage() dans une boucle, le thread conservera l'accès au processeur même si aucun message n'est présent dans la file d'attente. La charge processeur sera donc de 100 % même si l'application est inactive. Ce type d'erreurs ne doit donc surtout pas être commis sous peine de diminuer gravement les performances globales du système.

Il est également indispensable de comprendre qu'on ne peut jamais prévoir l'ordre dans lequel les threads recevront l'accès au processeur. Une application multithread devra toujours utiliser les fonctions de synchronisation (qui seront détaillées plus tard) et ne devra jamais tenter de prévoir l'ordre d'exécution des instructions. De plus, deux lignes consécutives d'une fonction ne seront pas forcément effectuées à la suite car le contrôle du processeur pourra être passé à d'autres threads entre temps. Il faut donc bien avoir en tête les contraintes imposées par un programme multithread avant de se lancer dans sa réalisation.

V-D. Synchronisation

V-D-1. Cours théorique

Les fonctions de synchronisation sont indispensables dans un programme multithread. La mauvaise synchronisation d'une application peut conduire à des plantages ou à des performances totalement désastreuses.

Étudions l'exemple d'un processus contenant deux threads, chacun utilisant le même tableau global. Un tel programme peut donner des résultats surprenant dans le cas d'une mauvaise synchronisation. Par exemple, deux évaluations consécutives de la même variable pourront retourner des valeurs différentes. Ce genre de comportement n'est généralement pas souhaité. Les deux threads devront alors se synchroniser avant d'accéder aux ressources partagées. Dans ce cas, la synchronisation consistera en une exclusion mutuelle (d'autres types de synchronisation seront étudiées plus tard). L'exclusion mutuelle consiste à faire patienter tous les threads demandant l'accès aux données partagées tant que le thread les utilisant n'a pas déclaré qu'il avait terminé. Chaque thread utilise une fonction pour signaler qu'il entre dans une section protégée. Il utilisera ensuite une seconde fonction pour signaler qu'il sort de la section protégée. Si un autre thread demande à entrer dans un bloc protégé, il sera mis en attente. Le thread qui est entré dans la section protégée doit impérativement signaler sa sortie de la section protégée sinon les autres threads resteront en attente. L'accès aux données partagées doit donc être aussi bref que possible.

Les fonctions de synchronisation sont un très bon moyen de mettre des threads en attente tout en conservant une charge processeur nulle. Si un thread entre dans une phase d'attente, son utilisation processeur restera très faible durant toute la période d'attente. Le système lui accordera de nouveau le contrôle dès que cela sera possible.

La synchronisation des threads devra donc toujours être effectuée par des fonctions de l'API et jamais par des boucles. L'utilisation de boucles ne repassant pas le contrôle au système rendrait les performances désastreuses et ralentirait l'exécution du processus lui-même car l'accès du processeur serait accordé à des threads en attente, ce qui ne présente aucun intérêt.

V-E. Premier exemple

V-E-1. Cours théorique

Ce premier exemple montre comment créer un thread. Le point d'entrée du thread peut être n'importe quel fonction respectant un certain typage. Dès la fonction constituant le point d'entrée retourne, le thread est terminé par le système.

La fonction CreateThread() qui démarre le thread retourne un HANDLE ainsi qu'un identifiant. L'identifiant est une valeur de type DWORD. Dans cet exemple, le HANDLE et l'identifiant ne nous seront d'aucune utilité car le thread se terminera spontanément. Le HANDLE ne sera donc même pas récupéré. Il est indispensable de passer un pointeur non nul pour l'identifiant. Dans le cas contraire, la fonction CreateThread() échouera. L'identifiant ou le HANDLE sont utilisés pour communiquer avec le thread.

Notons que l'ensemble des variables globales est accessible à la procédure du thread.

Télécharger le projet commenté.

V-F. Arrêt d'un thread

V-F-1. Cours théorique

Le problème de l'arrêt d'un thread est délicat. Un thread peut s'arrêter spontanément en terminant sa fonction principale. Dans ce cas, on considérera que l'arrêt était prévu. Dans le cas où l'application doit se terminer, l'arrêt ne peut pas être prévu. Dans ce cas, il existe différentes méthodes de terminer les threads en cours.

Voici tout d'abord la méthode la plus évidente, et aussi la plus mauvaise. La fonction TerminateThread() fournie par l'API permet de terminer un thread. Le thread est alors immédiatement stoppé. Bien que cette fonction soit fournie en standard avec l'API Windows, elle ne sera en pratique quasiment jamais utilisée. Son utilisation doit être réservée à des cas très particuliers et en connaissance de cause. Comme ces cas sont généralement très rares on peut considérer que dans un programme bien conçu, cette fonction ne devra jamais être utilisée. En effet, cette fonction termine le thread courant sans appeler les fonctions de désallocation de mémoire. Ceci est dû au fait qu'il n'existe pas de point de sortie prédéfini à un thread. La fonction TerminateThread() ne peut donc pas effectuer la désallocation de mémoire. Toute la mémoire allouée de manière dynamique sera donc perdue. De même, les dlls chargées par le thread ne seront pas libérées… Cette fonction ne doit donc être utilisée que dans le cas d'un thread n'utilisant que des allocations de mémoire statique, et aucune ressource extérieure…

Pour éviter l'utilisation de cette fonction, chaque thread doit être muni d'un ou de plusieurs points de sortie. Les systèmes de communication inter-threads seront utilisés pour mener le thread vers ce point de sortie. De cette manière la désallocation de la mémoire ainsi que la libération des ressources extérieures pourra être effectuée. La communication inter-threads sera traitée plus tard. On peut tout de même citer les variables globales et les messages qui sont les principaux moyens de communication entre threads. Bien que plus complexes à mettre en place, les méthodes de communication inter-threads seront donc toujours à préférer.

L'arrêt de l'application entraîne automatiquement l'arrêt de l'ensemble des threads. Le système appellera alors la fonction TerminateThread() pour arrêter les threads encore actifs. Une application doit donc toujours s'assurer de l'arrêt de l'ensemble des threads avant de se terminer. Les fonctions de synchronisation sont très adaptées à ce type de tâche, comme la fonction WaitForMultipleObjects() qui sera vue en détails plus tard.

V-G. Récupération des messages

V-G-1. Cours théorique

La récupération des messages peut avoir deux fonctions : récupérer les messages de communication liés à une fenêtre ou récupérer les messages de communication inter-thread. La réception des messages doit être effectuée de manière différente selon le type de messages. De manière générale, les messages associés à des fenêtres doivent être traités avec la fonction GetMessage() dans une boucle. Cette fonction permet de passer le contrôle aux autres threads du système dès que la file d'attente est vide. De cette manière les performances sont optimales. De plus, les messages associés aux fenêtres doivent être traités rapidement. L'utilisation de cette fonction dans une boucle permet donc un traitement rapide tout en conservant de bonnes performances.

Le traitement des messages dans le cadre de la communication inter-thread est différent (il sera vu plus en détail plus tard). Il concerne par exemple l'envoi d'un message d'abandon de la tâche courante. La fonction GetMessage() est très peu adaptée au traitement de ce type de messages car elle est bloquante. Le thread sera donc suspendu tant que la file d'attente sera vide. Un thread ne s'occupant pas de la réception des messages associés à une fenêtre a généralement une tâche bien précise à exécuter. Il serait donc absurde d'utiliser une fonction bloquante pour le traitement des messages. De plus, ces messages ne nécessitent pas un traitement rapide. On préférera donc utiliser la fonction PeekMessage() qui elle n'est pas bloquante. Il faudra donc utiliser cette fonction dans une boucle. Comme l'utilisation de cette fonction dans une boucle provoque de graves problèmes de performances, il ne faudra pas effectuer cette boucle trop fréquemment. Prenons l'exemple d'un thread gérant un transfert de fichier. Le transfert de fichier est réalisé par une boucle qui lit les données puis les écrit à une place différente. Selon la durée moyenne d'une boucle, on déterminera un intervalle d'appel de PeekMessage(). Par exemple, toutes les 100 itérations de la boucle principale, la fonction PeekMessage() sera appelée dans une boucle jusqu'à épuisement de la fille d'attente. Ce type de traitement est adapté à un faible nombre de messages, de manière à ne pas ralentir le travail de la boucle principale. En supposant que la durée moyenne d'une itération de la boucle soit de 5ms, les messages seront donc traités 2 fois par seconde. Si un message demandant l'arrêt de la tâche en cours est envoyé, il sera traité au plus 500ms plus tard, ce qui est très acceptable. De plus, cette technique ne nuit que très peu aux performances de la boucle principale puisque seulement une itération sur 100 sera sensiblement ralentie.

Si la rapidité du traitement des messages n'a que peu d'importance on pourra espacer les appels à PeekMessage() de manière à conserver autant que possible les performances de la boucle principale. Cette fonction sera donc particulièrement adaptée au traitement de messages de notifications envoyés à des thread effectuant une tâche précise.

Dans le cas d'un thread effectuant des tâches 'sur commande' on préférera bien entendu la fonction bloquante GetMessage() puisque le thread sera en attente de tâche (et ne consommera donc pas de ressources).

V-H. Communication inter-threads

V-H-1. Cours théorique

La communication inter-threads permet aux différents threads d'une application de dialoguer entre eux. Comme la réception des messages peut poser problème, on tentera généralement de réduire au minimum la communication inter-threads et donc de réaliser des threads aussi indépendants que possible.

Outre les fonctions de synchronisation qui ne sont pas à proprement parler des fonctions de communication, il existe deux moyens principaux de communication : les variables globales et les messages. Les variables globales sont un moyen de communication facile à mettre en place. Toutefois, leur utilisation devra être faite avec rigueur pour éviter des problèmes dus à l'accès de variables partagées. De plus dans certains cas, l'utilisation de variables globales peut nuire aux performances.

Une variable globale ne doit jamais être utilisée comme moyen de synchronisation. En effet, il est possible de placer la valeur d'une variable globale à 0 et d'effectuer une boucle avec un test sur cette variable de manière à attendre tant que la valeur reste à 0. Un second thread peut alors débloquer l'attente en modifiant la valeur de cette variable globale. Bien que fonctionnant, cette méthode ne devra jamais être utilisée car elle provoque une forte charge processeur inutile. Dans ce cas, les fonctions de synchronisation devront être utilisées car elles permettent de laisser l'accès processeur au reste du système tant que le thread est en attente.

De plus, deux threads ne doivent jamais accéder en écriture à une même variable globale. Ce type d'accès peut provoquer des plantages dus à la manière dont les variables globales sont utilisées (ces variables sont parfois placées dans des registres pour optimisation). L'utilisation de variables globales pour la communication inter-threads implique une communication à sens unique. Un thread accède à cette variable en écriture et les autres threads réagissent en fonction de cette valeur. Une telle utilisation ne posera pas de problèmes. Le partage de données mémoire en écriture implique des précautions particulières comme la synchronisation.

Les variables globales pourront donc être utilisées comme drapeau. Par exemple pour signifier à un thread qu'il doit se terminer. Si le thread effectue une boucle, on peut placer dans cette boucle un test sur la variable globale. Le thread principal place cette variable à une valeur principale au moment de quitter, ce qui provoque l'arrêt des autres threads. Notons tout de même que le thread principale devra attendre que l'ensemble des threads ait terminé avant de stopper l'application. En effet, l'arrêt de l'application provoque une fermeture de tous les threads en cours (ce qui revient à un appel de TerminateThread()). Il faudra donc placer un système d'attente dans le thread principal (fonctionnant via d'autres variables globales ou par un système de messages de notification).

L'utilisation de variables globales est donc une méthode rapide de communication. Toutefois cette méthode est soumise à un certain nombre de contraintes qui ne doivent surtout pas être ignorées.

Les messages constituent la deuxième méthode de communication. La fonction PostThreadMessage() permet d'envoyer des messages à un thread, même si celui-ci ne gère pas de fenêtres. Le thread pourra alors utiliser les fonctions GetMessage() ou PeekMessage() pour récupérer les messages. Bien entendu, il sera inutile d'utiliser les fonctions TranslateMessage() et DispatchMessage() puisque ces messages ne sont pas destinés à des fenêtres.

Il existe plusieurs méthodes pour créer des messages personnalisés. Tout d'abord, il est possible de définir une constante au niveau du préprocesseur (#define). Les valeurs valides pour les messages définis par l'utilisateur doivent être compris entre les valeurs WM_USER et 0x7FFF. La constante WM_USER est définie dans les headers Windows.

La deuxième méthode consiste à utiliser la fonction RegisterWindowMessage(). Cette fonction retourne une valeur utilisable pour un message en fonction d'une chaîne de caractère. Le système garantit l'unicité du message en fonction de la chaîne de caractère. Si deux applications distinctes utilisent cette fonction avec la même chaîne, elles obtiendront la même valeur. Cette fonction doit donc seulement être utilisée dans le cadre de la communication inter-applications, c'est à dire au niveau système. Si les messages sont locaux à l'application, il est recommandé de définir des constantes.

Si un thread désire communiquer une plus grande quantité d'information, il peut joindre au message 2 valeurs (lParam et wParam). Deux choix sont alors possibles. Utiliser ces valeurs comme paramètres ou joindre un pointeur. La méthode à utiliser pour joindre un pointeur est la suivante : le thread qui poste le message alloue dynamiquement de la mémoire et poste le pointeur. Le thread émetteur ne doit pas désallouer cette mémoire. C'est le thread récepteur qui s'occupera de la désallocation de la mémoire. Cette méthode assure que le pointeur sera toujours valide à la réception du message.

V-I. Sections Critiques

V-I-1. Cours théorique

Les sections critiques sont utilisées dans le cadre de la synchronisation, de manière à réaliser des exclusions mutuelles. Elles permettent de créer une section de code qui sera protégé, c'est à dire qu'un seul thread à la fois aura accès à ce code. Les sections critiques sont très utiles pour protéger des données partagées.

L'utilisation des sections critiques est relativement simple. Pour créer une section critique, l'application doit déclarer une variable de type CRITICAL_SECTION, généralement globale. Puis au moins un thread doit initialiser la section critique en appelant la fonction InitializeCriticalSection(). Cette fonction peut être appelée un nombre indéfini de fois. Par exemple, chaque thread qui va utiliser la section critique peut s'assurer qu'elle est valide en appelant cette fonction.

Deux fonctions seront ensuite utilisées pour signifier l'entrée et la sortie d'un bloc protégé. La fonction EnterCriticalSection() permet de signaler l'entrée dans un bloc protégé. Si un thread a déjà appelé cette fonction et n'est pas encore sorti du bloc protégé, cette fonction mettra en attente n'importe quel autre thread. Le même thread peut appeler cette fonction plusieurs fois sans être bloqué, mais il devra alors appeler la fonction de sortie de bloc un nombre de fois égal pour indiquer sa sortie du bloc protégé.

Un thread indique qu'il sort d'un bloc protégé en appelant la fonction LeaveCriticalSection(). Un des threads en attente sera alors débloqué et pourra à son tour exécuter le bloc protégé.

V-J. Fonctions d'attente

V-J-1. Cours théorique

Les fonctions d'attente permettent d'attendre de manière explicite qu'un événement se produise. Les fonctions d'attente sont WaitForSingleObject() et WaitForMultipleObjects(). Ces fonctions permettent d'attendre que l'état d'un objet change. Le thread appelant une de ces fonctions peut définir un temps maximal d'attente (qui sera éventuellement infini).

Différents objets peuvent être utilisés. Par exemple, la fonction WaitForSingleObject() peut être utilisée avec le HANDLE d'un thread. La fonction attendra alors que le thread se termine pour retourner. Pour attendre que plusieurs threads se terminent, une application pourra alors appeler la fonction WaitForMultipleObjects() en spécifiant qu'elle désire attendre que l'ensemble des threads ait terminé leur exécution. Ces fonctions mettent le thread qui les appelle en attente et sont donc excellentes au niveau performance. Leur utilisation est donc hautement recommandée.

Un événement créé explicitement peut également être utilisé avec ces fonctions. Ce cas sera étudié dans la partie consacrée aux événements. De même, le HANDLE d'un processus peut être utilisé de manière à attendre qu'il se termine.

Dans le cas de la communication inter-threads nous avons vu qu'une application pouvait utiliser une variable globale pour spécifier à ses threads qu'ils devaient se terminer. L'application doit ensuite attendre que les threads se terminent avant de quitter. Cette attente sera typiquement réalisée par un appel à la fonction WaitForMultipleObjects(). Lorsque cette fonction retournera, l'application sera alors assurée que l'ensemble de ses threads auront terminés et pourra quitter sans risque.

V-K. Événements

V-K-1. Cours théorique

Les événements peuvent être utilisés dans les fonctions d'attente. L'API Windows fournit des fonctions permettant de créer des événements de manière explicite. L'état de ces événements pourra ensuite être modifié de manière explicite. L'état d'un événement peut être signalé ou non signalé. Lorsqu'un événement est en état non signalé, l'appel à une fonction d'attente provoquera la suspension du thread appelant jusqu'à ce que l'événement passe en état signalé.

Les fonctions CreateEvent() et CloseHandle() peuvent être utilisées pour la création et la destruction d'événements. Les fonctions SetEvent(), ResetEvent() et PulseEvent() pourront ensuite être utilisées pour modifier l'état de l'objet.

Les événements peuvent être configurés de différentes manières. La configuration d'un événement se fait lors de sa création. Si l'objet est en mode 'réinitialisation manuelle' il restera en état signalé tant que son état ne sera pas explicitement réinitialisé grâce à la fonction ResetEvent(). Si plusieurs threads étaient en attente de cet objet, ils seront alors tous libérés au moment où l'état de l'objet passera à 'signalé'.

Si l'objet est en mode 'réinitialisation automatique', son état sera automatiquement replacé à 'non signalé' dès qu'un thread en attente sera libéré. Si plusieurs threads sont en attente d'un même événement, alors un seul thread sera libéré eu moment ou l'état sera passé à 'signalé'.

Les événements sont un moyen pratique de synchroniser plusieurs threads de manière explicite.

Les événements peuvent par exemple être utilisés pour mettre un thread en attente tandis qu'un autre thread initialise des données. Dès que les données seront initialisées, le ou les threads seront débloqués par le thread ayant initialisé les données.

Tout comme les sémaphores, les événements peuvent être nommés ou non. Si l'événement est nommé, son nom doit être unique dans l'ensemble du système. Si une autre application tente de créer un événement du même nom, la fonction CreateEvent() retournera un HANDLE sur l'événement précédemment créé. Ceci peut être utilisé pour synchroniser des applications entre elles.

V-L. Sémaphores

V-L-1. Cours théorique

Les sémaphores sont utilisés pour limiter le nombre de threads en fonctionnement. Prenons le cas d'un serveur. On désire limiter le nombre maximal de connections simultanées. Dans ce cas, on utilisera la synchronisation fournie par les sémaphores.

Pour utiliser un sémaphore, une application doit créer un objet de type sémaphore en appelant la fonction CreateSemaphore(). Un sémaphore est constitué d'un compteur. Le statut d'un sémaphore est signalé lorsque le compteur est supérieur à 0 et non signalé lorsque le compteur est à 0. Lors de la création du sémaphore, on spécifie la valeur initiale du compteur, ainsi que sa valeur maximale. La valeur maximale du compteur correspond au nombre maximum de threads qui pourront être exécutés simultanément.

Chaque thread désirant se synchroniser doit utiliser une fonction d'attente (WaitForSingleObject() par exemple). Lorsqu'un thread appelant une fonction d'attente est libéré, le compteur du sémaphore est décrémenté de 1. Dès que la valeur du compteur atteint 0, les threads appelant les fonctions d'attente seront bloqués. Lorsqu'un thread sort d'un bloc synchronisé, il doit appeler la fonction ReleaseSemaphore(). Cette fonction incrémente de 1 le compteur du sémaphore, permettant ainsi à un autre thread d'être débloqué.

Le compteur du sémaphore ne pourra jamais dépasser la valeur maximale spécifiée au début. De cette manière, on peut très facilement limiter le nombre de threads effectuant une tâche spécifiée, et conserver ainsi les performances système. Une augmentation trop importante du nombre de threads en exécution simultanée poserait en effet des problèmes de performances.

Il est possible de spécifier un nom lors de la création du sémaphore. De cette manière, des applications différentes peuvent se synchroniser. La première crée un sémaphore nommé. La seconde application tente ensuite de créer un sémaphore du même nom. Ceci aura pour effet de lui retourner un HANDLE sur le sémaphore déjà existant. Le nom d'un sémaphore doit donc être unique dans l'ensemble du système. Remarquons qu'un sémaphore ne doit pas nécessairement être nommé.

V-M. Trois projets simples

V-M-1. Cours théorique

Voici trois projets présentant l'utilisation des fonctions de synchronisation étudiées : sections critiques, événements et sémaphores. Ces trois projets ont pour but d'expliquer l'utilité des différents types de synchronisation. De plus, ils pourront aider à comprendre la mise en place d'une synchronisation rudimentaire.

Il ne faut jamais oublier que les accès à la console Windows doivent être synchronisés de manière à éviter des résultats totalement imprévisibles. Une section critique est parfaitement adaptée pour résoudre ce genre de problèmes.

Télécharger les projets commentés :Projet 09 - Projet 10 - Projet 11 .

V-N. Performances

V-N-1. Cours théorique

La création d'applications multithread est généralement très performante. Toutefois, il faudra faire attention à préserver les performances systèmes. La synchronisation devra toujours être effectuées grâce aux objets fournis par l'API comme les sections critiques, les événements ou encore les sémaphores. Il faut toujours penser qu'un thread inactif doit laisser le contrôle au système et non pas effectuer une boucle. Bien qu'elles fonctionnent parfaitement, les boucles sont désastreuses au niveau performances système et on perd rapidement l'intérêt de l'application multithread.

Il ne faut pas non plus tenter de segmenter à tout prix les tâches. Un nombre trop important de threads peut nuire aux performances système. Le travail qui peut être effectué par un thread ne doit donc pas être confié à 2 threads.

Les applications clients réseau sont souvent très performantes grâce au multithreading. En effet, le fait d'établir plusieurs connections avec la machine hôte permet d'améliorer le débit réseau. Toutefois, un nombre trop important de threads finira par nuire aux performances de l'application car le système perd du temps en passant le contrôle du processeur à un nombre trop important de threads. Les applications sont alors considérablement ralenties.

De plus, la réalisation d'applications multithread est souvent source d'erreurs. En effet, il ne faut jamais oublier que des instructions situées dans des threads seront exécutées dans un ordre quelconque. Il ne faut jamais tenter de prévoir l'ordre d'exécution. A partir du moment où l'ordre d'exécution n'est pas certain, il faudra impérativement utiliser les fonctions de synchronisation.

Il faut également comprendre que le fait d'utiliser des fonctions de synchronisation nuit aux performances de l'application. Les fonctions de synchronisation ne doivent donc pas être utilisées si elles ne sont pas nécessaires. De plus, si un nombre important de threads partage des données, le temps d'attente pour obtenir l'accès à ces données va rapidement devenir important. Chaque thread passera donc un temps important à attendre, ce qui est d'autant plus problématique si la tâche qu'il doit effectuer est courte.

Il faut également éviter impérativement des entrées - sorties successives dans des sections critiques. En effet, si le temps d'exécution entre les sections critiques est trop court, le thread aura très rapidement un temps d'attente supérieur à son temps de calcul. Ce problème se manifestera encore plus avec un nombre important de threads. Si un thread doit faire des accès successifs à des données partagées, il peut copier ces données dans un espace non partagé ou rester dans la section critique le temps d'exécuter sa tâche. Si le temps d'exécution de la tâche est court, un thread aura tout intérêt à ne pas sortir de la section critique.

Il faut donc impérativement prendre en compte les questions de performance lors de la réalisation d'applications multithread. De plus, les plantages dus à des problèmes de partage de données ne surviendront pas forcément lors des tests car les threads ne s'exécutent jamais de la même manière. Il faut donc prévoir les problèmes de partage et non pas compter sur les tests pour les déceler.


Programmation avec l'API Windows

Table des matières

  • VI. Annexes
    • VI-A. Annexe A : Les contrôles
      • VI-A-1. Styles des contrôles
      • VI-A-2. Messages utilisables avec les contrôles
    • VI-B. Annexe B : Fonctions par thèmes

VI. Annexes

VI-A. Annexe A : Les contrôles

Vous pourrez trouver ici des extraits de l'aide fournie par Microsoft sur les styles des différents contrôles. Ces extraits sont fournis tels quels, comme vous pourriez les trouver sur msdn.microsoft.com. Certains ont été remis en page.

VI-A-1. Styles des contrôles

Edit Styles
Button Styles
List Box Styles
Combo Box Styles
Progress Bar Styles

Bitmap Functions
Coordinate Space and Transformation Functions
Cursor Functions
Device Context Functions
Dialog Box Functions
File Functions
Font and Text Functions
Keyboard Input Functions
Line and Curve Functions
Menu Functions
Message and Message Queue Functions
Painting and Drawing Functions
Path Functions
Resource Functions
Timer Functions
Window Functions

VI-A-2. Messages utilisables avec les contrôles

List Box
Progress Bar
Combo Box

VI-B. Annexe B : Fonctions par thèmes

Vous pourrez trouver ici des extraits de l'aide fournie par Microsoft. Ces extraits sont fournis tels quels, comme vous pourriez les trouver sur msdn.microsoft.com. Certains ont été remis en page.

Bitmap Functions
Coordinate Space and Transformation Functions
Cursor Functions
Device Context Functions
Dialog Box Functions
File Functions
Font and Text Functions
Keyboard Input Functions
Line and Curve Functions
Menu Functions
Message and Message Queue Functions
Painting and Drawing Functions
Path Functions
Resource Functions
Timer Functions
Window Functions


Cours sur L'API Winodos de openclassrooms

    Apprentissage de l'API Windows

    Mis à jour le lundi 26 mai 2014
    • Programmation Windows

    Vous voudriez apprendre l'API Windows et vous trouvez les autres cours présents sur le net trop difficiles ou pas assez précis ? Vous avez frappé à la bonne porte.

    Bienvenue dans ce tutoriel. Vous allez apprendre ici à vous servir d'une API très développée : l'API Win32. Sa proximité avec le système d'exploitation lui permet d'avoir un contrôle total sur n'importe quelle partie de l'OS, et un grand éventail de fonctions.

    L'API Windows est, malheureusement et comme son nom l'indique, exclusivement dédiée à Windows. Donc, si vous possédez un autre OS, passez votre chemin.

    Je considère dans ce tutoriel que vous ne connaissez strictement rien à l'API Windows, ceci pour ne pas décourager les débutants ; néanmoins, vous aurez besoin des connaissances acquises tout au long du tuto de M@teo21. Vous trouverez donc ici des cours évolutifs, entrecoupés de TP réguliers, une documentation sur les différents messages, notifications, styles, fonctions, etc., et même un glossaire ;) .

    Toutes ces parties seront progressivement remplies au fur et à mesure de la rédaction du tutorial en lui-même. J'ai pour cela des coéquipiers, respectivement pamaury et acryline.

    Si vous avez des problèmes, des questions, ou encore des suggestions, vous pouvez nous envoyer un MP, auquel on se fera un plaisir de répondre. ;)

    Les termes touchant à la programmation que vous êtes susceptibles de ne pas comprendre seront de couleur rouge, et seront définis dans le glossaire.

    Sur ce, je vous souhaite une agréable lecture, et j'espère que ce tuto vous aidera le plus possible. :)

    • Partie 1 - Grandes lignes

      1. Les bases

      2. Les contrôles

      3. L'affichage

      4. TP : Un lecteur audio

      5. Les événements

      6. Esthétique avancée

    • Partie 2 - Annexes

      1. Documentation

      2. Glossaire

      3. Manipulation du registre

      4. Divers

    • Partie 3 - Projets

      1. Programmes

    • Ce cours n'est pas encore certifiant.

    déroulement d'un cours

    • 1

      Dès aujourd'hui, vous avez accès au contenu pédagogique et aux exercices du cours.

    • 2

      Vous progressez dans le cours semaine par semaine. Une partie du cours correspond à une semaine de travail de votre part.

    • !

      Les exercices doivent être réalisés en une semaine. La date limite vous sera annoncée au démarrage de chaque nouvelle partie. Les exercices sont indispensables pour obtenir votre certification.

    • 3

      À l'issue du cours, vous recevrez vos résultats par e-mail. Votre certificat de réussite vous sera également transmis si vous êtes membre Premium et que vous avez au moins 70% de bonnes réponses.

    J'ai tout compris !

    L'auteur

    Pasquestion Etpuisquoiencore


Cours sur L'API Winodos de openclassrooms

Les bases

Ce chapitre va vous inculquer les bases du API Win32.

Commencement

Le nom de ce que vous allez apprendre vous a sûrement laissés perplexes : je vous ai dit que nous allions vous apprendre l'API Windows, mais vous ne savez peut-être toujours pas de quoi il s'agit.

Une API, ou Application Programming Interface est, comme son nom l'indique, une interface de programmation : elle contient un ensemble de fonctions bas niveau, ici extrêmement bien documentée (comme vous pouvez le constater), permettant de programmer des applications haut niveau. Elles sont alors accessibles à partir de bibliothèques intégrées au projet.

Pour pouvoir utiliser le WinAPI, vous devez évidemment sélectionner un projet WinAPI dans votre IDE pendant la création du nouveau projet. Par exemple, sous code::blocks, vous devez sélectionner "Win32 GUI Application" comme sur cette photo :

Vous n'aurez rien à linker pour le moment.

Comme vous pouvez vous en douter, vous devrez inclure la bibliothèque appropriée pour utiliser cette API, à savoir :

#include <windows.h>

Comme chacun sait, une seule fonction est commune à tous les programmes : le main. Le point d'entrée du programme est, dans cette API, un peu plus compliqué que dans un projet console ou SDL. Ainsi, le code minimal que vous pouvez faire en WinAPI est le suivant :

#include <windows.h>

int WinMain (
                HINSTANCE cetteInstance,
                HINSTANCE precedenteInstance,
                LPSTR lignesDeCommande,
                int modeDAffichage
            )
{
   return 0;
}

:waw: C'est quoi, tout ça ? Où diable sont passés les argc et argv tout bêtes ?

Pas de panique. ;) Étudions ensemble cette fonction. Vous connaissez forcément le type de retour de cette fonction : il s'agit d'un int. Passons maintenant aux arguments :

  • Le premier argument est l'instance actuelle de votre programme. C'est en quelque sorte une "boîte" représentant votre programme, et imbriquant tout ce qu'il contient (pour ne pas vous encombrer la tête) :

    HINSTANCE cetteInstance

  • Le deuxième argument désigne l'instance précédente. En fait, ce paramètre date de Windows 16 bits, et donc obsolète. Il vaut donc toujours NULL :

    HINSTANCE precedenteInstance

    Conséquence : vous n'êtes même pas obligés d'indiquer un nom de variable, bien que le type soit quand même nécessaire.

  • Le troisième argument remplace justement argv que vous connaissez déjà dans le prototype du main console ou SDL. Cependant, il est dans sa forme brute ; vous ne pourrez donc pas l'utiliser de suite (je vous expliquerai plus tard comment la découper et l'utiliser) :

    LPSTR lignesDeCommande

    Il est d'un type que vous ne connaissez pas, puisqu'il s'agit d'une structure signée Microsoft. Celle-ci correspond à un bête char*.

  • Le petit dernier ne vous servira probablement pas souvent : il s'agit du mode d'affichage (visible, invisible, minimisé etc...) de la fenêtre principale :

    int modeDAffichage

    Même remarque que pour le paramètre contenant l'instance précédente.

Pour ceux qui écoutent les conseils du rédacteur -à savoir moi ;) -, vous devriez au préalable savoir à quoi correspondent les types principaux utilisés par cette API :

  • LPSTR correspondant à un char *, et ses déclinaisons :

    • LPCSTR correspondant à un const char *.

    • LPTSTR correspondant à un TCHAR * (voir TCHAR plus bas).
      On l'initialise avec la macro TEXT, selon :

      LPTSTR string = TEXT("exemple");

    • LPWSTR correspondant à un wchar_t *
      On l'initialise avec le préfixe ' L', selon :

      LPWSTR string = L"exemple";

    Vous pouvez combiner les lettres définissant un caractère particulier de LPSTR pour obtenir des types adaptés (comme LPCTSTR).

  • WORD correspondant à un unsigned short

  • DWORD correspondant à un unsigned long

  • TCHAR correspondant à :

    • En compilant en Unicode : un wchar_t

    • Sinon : un char ;)

    Il est recommandé d'utiliser les TCHAR partout (même si ce n'est pas fait dans ce tuto), du moment que l'on n'utilise pas stdio, ou certaines fonctions de cette API (dont je vous mettrai en garde si c'est le cas), pour des raisons de compatibilité avec n'importe quel IDE.

    Seuls les utilisateurs de VS en général compilent en Unicode. Si n'en voulez pas (ça simplifie les choses), voici la manip' à exécuter : Project Properties -> Configuration Properties -> General -> Character set -> Changer l 'option de "unicode" à "multi-byte".

Et voilà pour une présentation restreinte ^^ .

Vous savez maintenant ne rien afficher sans erreur de compilation ! :lol: Génial, non ? Vous en voulez plus ? (pas étonnant ;) ) D'accord, direction "Votre première fenêtre" :) .

Pour ceux qui veulent absolument un "Hello World" avant de commencer (ça les frustre sûrement), voici la formule magique à rajouter dans le WinMain :

MessageBox(NULL, "Hello World", "Hello World", MB_OK);

Ou plus propre :

MessageBox(NULL, TEXT("Hello World"), TEXT("Hello World"), MB_OK);

Rédacteur : Mg++

Votre première fenêtre

Vous êtes impatient de créer votre première fenêtre ? Tant mieux, c'est ici que ça se passe. :p

Le minimum

Premièrement, munissez-vous du code minimal que je vous ai donné tout à l'heure :

#include <windows.h>

int WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance, LPSTR lignesDeCommande, int modeDAffichage)
{
   return 0;
}

Dans certains cas, vous serez contraints de rajouter les mots clés WINAPI ou APIENTRY entre 'int' et 'WinMain', cause d'une erreur de compilation de type "WinMain already defined in...". Si c'est le cas, adaptez mes codes à cette situation.

Les variables nécessaires

Nous aurons besoin pour afficher la fenêtre de plusieurs variables :

HWND fenetrePrincipale;
MSG message;
WNDCLASS classeFenetre;

Étudions d'abord leur type respectif et leurs rôles :

  • fenetrePrincipale est de type HWND, qui veut dire handle de fenêtre. Si vous connaissez un minimum l'anglais, vous remarquerez que l'on pourrait le traduire par poignée.

    Quel est le rapport ?

    Réfléchissez : fenetrePrincipale sera une sorte de "poignée" nous permettant d'agir sur la fenêtre principale. En outre, cette structure possède sur la fenêtre toutes les informations nécessaires à son affichage. Bien sûr, ce handle ayant un nom, cela nous permet d'identifier, par le nom d'une variable, la fenêtre créée (bref : c'est bien pratique :) ).

  • message, quant à lui, est comme son nom l'indique un message système, utilisé pour communiquer entre l'utilisateur et les fenêtres : il s'agit en fait du descriptif d'un événement. Si vous connaissez la SDL, vous pourriez faire la correspondance avec 'SDL_event'.

  • classeFenetre est la classe de fenêtre (décidément, je choisis bien les noms ;) ) nécessaire à la construction de la fenêtre principale. C'est en fait une liste d'informations permettant par la suite de créer la fenêtre selon notre bon vouloir. (Je précise que la "classe" n'a pas ici la même définition que pour le C++.)

Remplissage de la classe de fenêtre

La création de la fenêtre demande le remplissage de sa classe, ou WNDCLASS. Nous allons donc nous en occuper :

  • Le champ 1 définit le style à affecter à la fenêtre. Ici, il n'y en a pas :

    classeFenetre.style=0;

  • Le champ 2 définit la fonction callback à utiliser pour cette fenêtre (je vous explique plus loin) :

    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;

  • Le champ 3 définit combien de bytes en plus seront alloués à la suite de la structure (cela nous est inutile, donc 0 ^^ ) :

    classeFenetre.cbClsExtra = 0;

  • Le champ 4 définit la même chose, mais suite à l'instance (donc aussi inutile) :

    classeFenetre.cbWndExtra = 0;

  • Le champ 5 définit l'instance à laquelle appartient la fenêtre :

    classeFenetre.hInstance = cetteInstance;

  • Le champ 6 définit l'icône à utiliser pour la fenêtre (en haut à gauche). Elle doit être chargée soit en passant par les ressources (nous verrons cela plus tard), soit, comme suit, avec une icône inclue dans Windows, possédant un ID spécifique (la liste sera peut-être proposée dans notre documentation) :

    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);

  • Le champ 7 définit le curseur par défaut à utiliser pour cette fenêtre (même chose que pour les icônes : le curseur utilisé ici est inclus dans Windows, et choisi parmi une petite liste) :

    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);

  • Le champ 8 définit la couleur à utiliser pour le fond de la fenêtre. Son type est HBRUSH, (traduit par brosse), et peut être changé grâce à une fonction de conversion, RGB->HBRUSH, que nous verrons plus tard, dans le chapitre sur l'affichage :

    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);

  • Le champ 9 définit le menu associé à cette fenêtre (peut être ajouté d'une autre manière) : ici, il n'y en aura aucun :

    classeFenetre.lpszMenuName =  NULL;

  • Et enfin le le champ 10, qui sera le nom de la classe en question :

    classeFenetre.lpszClassName = "classeF";

Maintenant que nous avons rempli la classe de la fenêtre, encore faut-il la sauvegarder. Voici comment faire :

RegisterClass(&classeFenetre);

Création proprement dite de la fenêtre principale

Enfin ! Nous sommes prêts à créer notre fenêtre. C'est le travail de la fonction, dont voici le prototype :

HWND CreateWindow(
                        LPCTSTR lpClassName,
                        LPCTSTR lpWindowName,
                        DWORD dwStyle,
                        int x,
                        int y,
                        int nWidth,
                        int nHeight,
                        HWND hWndParent,
                        HMENU hMenu,
                        HANDLE hInstance,
                        LPVOID lpParam
               );

:euh: Ne me dis pas que l'on doit retenir toutes ces fonctions ?

Bon d'accord, elles sont lourdes... mais non, vous n'êtes pas obligés de les retenir. Mais à force de les utiliser (vous verrez ;) ), vous les connaîtrez par coeur. "C'est en forgeant qu'on devient forgeron" à ce qu'il paraît. :p

Bon. Déjà, vous pouvez remarquer que son type de retour est HWND. Vous pouvez faire la correspondance avec fenetrePrincipale, et deviner que c'est celle-ci qui va contenir le retour de CreateWindow ^^ . En fait, cette fonction remplit la structure HWND avec les informations propres à la fenêtre, et la crée par la même occasion. (CreateWindow ne crée pas vraiment que des fenêtres au sens connu, mais toutes sortes de contrôles, que ce soit : fenêtre, bouton, listbox, progressbar, etc.)

Maintenant, occupons-nous de ses arguments :

  • L'argument 1 désigne la classe de fenêtre à partir de laquelle la fenêtre va être créée :

    LPCTSTR lpClassName

    (Vous avez peut être remarqué qu'il est de type LPCTSTR... C'est en fait la même chose que LPSTR mais constant, donc égal à const char[].)

  • L'argument 2 désigne le nom de la fenêtre, c'est-à-dire, dans le cas d'une fenêtre, la string qui va être affichée en guise de titre :

    LPCTSTR lpWindowName

  • L'argument 3 désigne les styles à donner à la fenêtre :

    DWORD dwStyle

    (Vous avez sûrement remarqué que le type de cet argument vous est inconnu : en fait, il est utilisé un peu partout, et correspond à un unsigned long. Il est souvent utilisé pour les styles / flags, et sûrement construit à partir d'une association de bits.)

  • Les arguments 4 à 7 désignent respectivement : les coordonnées du coin supérieur gauche, la largeur, ainsi que la hauteur de la fenêtre :

    int x,
int y,
int nWidth,
int nHeight

  • L'argument 8 désigne le handle de la fenêtre parent :

    HWND hWndParent

  • L'argument 9 désigne, dans le cas d'une fenêtre, le menu associé à celle-ci, et dans le cas d'un contrôle, son ID (pour pouvoir le manipuler) :

    HMENU hMenu

  • L'argument 10 désigne l'instance du programme :

    HANDLE hInstance

    (HINSTANCE dérive de HANDLE, donc son passage en tant que HANDLE se déroule sans histoire.)

  • L'argument 11 désigne les éventuels paramètres à envoyer à la fenêtre, à sa création :

    LPVOID lpParam

    (LPVOID est en fait un équivalent à void*. Pour ceux ayant fait de la SDL, cet argument possède le même rôle que le paramètre optionnel de type void* des timers.)

Et maintenant que vous connaissez chacun des arguments de la fonction CreateWindow, passons au codage pour créer une fenêtre :

fenetePrincipale = CreateWindow("classeF",
"Première fenêtre en winAPI !",
WS_OVERLAPPEDWINDOW, /*Style qui permet d'avoir une fenêtre tout ce qu'il y a de plus normale : barre de titre, menu système (réduire, maximiser, fermer), bordure etc...*/
CW_USEDEFAULT, // Permet à l'OS de déterminer dynamiquement la position de la fenêtre
CW_USEDEFAULT, //Idem
400,
300,
NULL, // Pas de fenêtre parent, puisque c'est la principale
NULL, //Aucun menu ne lui est associé
cetteInstance,
NULL); // Aucun paramètre à lui envoyer en plus

Mais la fenêtre ne s'affichera pas pour autant. :o Elle est créée, mais c'est tout. ^^

Notez qu'elle aurait pu l'être en possédant le style WS_VISIBLE.

Pour l'afficher, il faut appeler la fonction ShowWindow ! Celle-ci prend en premier paramètre le handle de la fenêtre dont on veut changer le mode d'affichage, et un integer déterminant son mode. Vous pouvez soit utiliser le paramètre de WinMain modeDAffichage, soit utiliser la valeur qu'il prend d'habitude : SW_SHOW. Donc :

ShowWindow(fenetrePrincipale,SW_SHOW);

Maintenant que la fenêtre est en mode "affichée", il faut rafraîchir l'écran afin de la montrer à l'utilisateur : c'est le rôle de UpdateWindow, prenant comme seul paramètre le handle de la fenêtre. Vous l'utiliserez sûrement beaucoup après avoir créé des contrôles enfants d'une fenêtre, afin de rafraîchir l'écran juste après leur création.

Boucle événementielle

Afin d'interfacer l'utilisateur avec la fenêtre, nous avons besoin d'une boucle événementielle traitant les messages créés par l'utilisateur (qui ne le sait même pas :p ) et destinés aux fenêtres concernées.
Pour cela, nous allons utiliser une nouvelle fonction que voici :

BOOL GetMessage(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax);

  • L'argument 1 désigne le message récupérateur des événements :

    LPMSG msg

    (Comme vous l'avez peut être deviné, les deux lettres 'LP' désignent un pointeur sur le type adjacent...)

  • L'argument 2 désigne le handle de fenêtre dont GetMessage va récupérer les événements (s'il est égal à NULL, GetMessage traitera toutes les fenêtres de l'instance) :

    HWND hwnd

Les deux autres paramètres ne sont pas très importants pour le moment (je ne voudrais pas que vous partiez avant votre première fenêtre ;) ). Mettez-les donc à zéro.

Donc, mettons en place la boucle :

while (GetMessage(&message, NULL, 0, 0)) /*Récupération des évènements de toutes les fenêtres de l'instance dans message*/
{
    TranslateMessage(&message); // Traduction de l'événement
    DispatchMessage(&message); // Envoi du message correspondant à la fenêtre concernée
}

La fonction callback

Cf glossaire

Vous vous rappelez le champ de structure de classeFenetre où on a donné le nom d'une fonction callback ? Eh bien définissons-la. ^^ Voilà déjà son prototype :

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam);

Son type de retour est assez spécial, mais j'ai choisi de ne pas vous le détailler ici, pour ne pas vous encombrer la tête avec des choses inutiles.

  • Le paramètre 1 désigne le handle de la fenêtre concernée :

    HWND fenetrePrincipale

  • Le paramètre 2 désigne le message envoyé par la boucle événementielle traitée :

    UINT message

  • Les paramètres 3 et 4 désignent des pointeurs d'int chargés d'apporter des précisions sur l'origine du message :

    WPARAM wParam, LPARAM lParam

Ces paramètres vous seront décrits un peu plus tard, quand nous parlerons plus en détail des messages créés par l'utilisateur.

La fonction callback étant un traitement des messages envoyés à la fenêtre, elle est chargée de faire telle ou telle action en fonction de tel ou tel message. C'est heureusement nous qui devons la coder. :p

Dans un premier temps, je vous propose de gérer les messages à l'intérieur de cette fonction de la manière suivante :

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_CREATE:
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(fenetrePrincipale, message, wParam, lParam);
    }
}

Le switch détermine quel message a été perçu par GetMessage :

  • WM_CREATE est envoyé à la création de la fenêtre. (Vous pourrez par ailleurs récupérer l'argument optionnel n°11 de CreateWindow à cet endroit.)

  • WM_DESTROY est envoyé lors de la destruction de la fenêtre. Par défaut (vous verrez que nous pouvons le changer), il est déclenché par WM_CLOSE, correspondant à un clic sur la croix en haut à droite que vous connaissez si bien ;) . Ici, il est traité, et PostQuitMessage est exécuté : il s'agit d'une fonction permettant de quitter simplement et proprement le programme. Elle prend comme unique argument 0.

  • Si aucun message déclaré explicitement n'est perçu, on retourne la procédure par défaut fournie par DefWindowProc.

Code complet

Et voilà : vous êtes prêts pour créer votre première fenêtre ! En combinant le tout, vous devriez avoir ce code-ci :magicien: :

#include <windows.h>

LRESULT CALLBACK procedureFenetrePrincipale(HWND, UINT, WPARAM, LPARAM);

int WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
LPSTR lignesDeCommande, int modeDAffichage)
{
    HWND fenetrePrincipale;
    MSG message;
    WNDCLASS classeFenetre;

    classeFenetre.style = 0;
    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
    classeFenetre.cbClsExtra = 0;
    classeFenetre.cbWndExtra = 0;
    classeFenetre.hInstance = NULL;
    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    classeFenetre.lpszMenuName =  NULL;
    classeFenetre.lpszClassName = "classeF";

    // On prévoit quand même le cas où ça échoue
    if(!RegisterClass(&classeFenetre)) return FALSE;

    fenetrePrincipale = CreateWindow("classeF", "Ma première fenêtre winAPI !", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
                                                   NULL, NULL, cetteInstance, NULL);
    if (!fenetrePrincipale) return FALSE;

    ShowWindow(fenetrePrincipale, modeDAffichage);
    UpdateWindow(fenetrePrincipale);

    while (GetMessage(&message, NULL, 0, 0))
    {
        TranslateMessage(&message);
        DispatchMessage(&message);
    }
    return message.wParam;
}

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_CREATE:
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(fenetrePrincipale, message, wParam, lParam);
    }
}

Et vous obtenez cette oeuvre magnifique (il ne faut pas exagérer quand même :-° ) :

Enfin, presque ;) . Vous aurez sûrement une barre de titre différente, à cause du thème de Windows.

Voilà, ce premier chapitre est terminé, j'espère qu'il vous a plu :) . Ne vous découragez surtout pas face à la longueur du code, car cela en vaut la peine, et cela va rentrer progressivement en tant qu'automatisme.

Rédacteur : Mg++

Elle est vide, ma fenêtre !

Pour le moment, votre fenêtre vous paraît sûrement vide et terne ... nous allons l'animer un peu ;) . Nous allons voir ici comment créer des boutons, comment les manipuler, et comment faire apparaître un message à l'utilisateur.

Déjà, incorporez ou chargez le code obtenu dans la dernière sous-partie afin d'afficher une fenêtre (changez aussi les dimensions de 300 de hauteur à 110, cela sera plus esthétique :) ).

Création des boutons

Je vous propose dans cette sous-partie de créer deux boutons : un permettant d'afficher une information à l'utilisateur, et un autre pour quitter (ce n'est pas pour rien que je l'incorpore, vous allez pouvoir avoir plus de précisions sur la gestion des messages).

Les boutons sont créés à partir d'une fonction que vous connaissez bien désormais : CreateWindow.

:-° Mais je croyais que c'était pour faire des fenêtres ?

Oui, mais les contrôles sont considérés comme des fenêtres, enfants (donc "attachées" si vous voulez) de la fenêtre parent. Seules les classes de fenêtres sont différentes selon le contrôle. Vous vous rappelez de la classe que nous avons créée pour la fenêtre principale, classeFenetre ? Eh bien, pour les contrôles, les classes sont prédéfinies. On trouve donc comme classes :

  • BUTTON : permettant de créer un bouton.

  • LISTBOX : permettant de créer une liste de choix.

  • STATIC : permettant toutes sortes de choses, comme des images, des groupbox, etc.

  • Et bien d'autres...

Vous verrez ces contrôles basiques lors du prochain chapitre.

Comme vous vous en doutez, nous allons donc utiliser la classe BUTTON. Avant de les créer, vous devez savoir une chose : les contrôles sont gérés (grâce aux messages, notifications [servant à avertir d'un événement secondaire]...) à partir soit d'un handle (pratique n'est ce pas ? ^^ ), soit d'un ID, défini grâce à :

#define ID 1

Où ID est généralement un mot l'identifiant bien (par convention, en majuscule), et suivi d'un nombre le remplaçant lors de la compilation. (Il faut avouer que recevoir le message ' CLIC ' est plus pratique que ' 02123 ' par exemple. Surtout quand les IDs se multiplient...)

Donc, nous allons d'abord créer les handles de boutons, dans un tableau pour plus d'accessibilité. Les contrôles sont déclarés au début du callback de la fenêtre à laquelle ils appartiennent :

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND boutons[2] = {NULL};

Veillez à bien mettre le mot-clé static lors de la déclaration d'une variable permanente : celui-ci permet à la variable d'avoir la même portée, soit, mais surtout une durée de vie commune à celle de la fenêtre.

C'est une méthode quelque peu barbare, surtout quand on a beaucoup de variables permanentes, mais j'ai choisi de vous en parler, car vous connaissez normalement déjà ce mot-clé ;) . Cependant, j'envisage de vous parler d'une autre méthode (meilleure) plus tard. Pour les curieux, il s'agit de la combinaison de SetWindowLong() avec GetWindowLong() (et leur déclinaison ~Ptr), permettant d'utiliser les membres de la WindowClass cbClsExtra et cbWndExtra.

Intégrez au préalable les définitions des ID des boutons au début de votre code :

#define ID_B_PARLER 0
#define ID_B_QUITTER 1

Dernière chose : vous vous rappelez le prototype de CreateWindow ? Cette fonction demandait l'instance. Or, la fonction callback n'entre pas dans la portée de cette variable. Nous sommes alors contraints d'en faire une variable globale.
Déclarez une variable globale comme suit :

HINSTANCE instance;

Et, en guise de première instruction (de préférence après les déclarations de variables) de WinMain, copiez cetteInstance dans instance comme suit :

instance = cetteInstance;

Bien. Maintenant, nous sommes prêts à créer les boutons. Cette opération se fait en général dans le WM_CREATE, puisqu'ils sont créés en même temps que la fenêtre parent :

case WM_CREATE:
                boutons[0] = CreateWindow("BUTTON", "Parler", WS_CHILD | WS_VISIBLE,
        5, 5, 383, 30, fenetrePrincipale, ID_B_PARLER, instance, NULL);
                boutons[1] = CreateWindow("BUTTON", "Quitter", WS_CHILD | WS_VISIBLE,
        5, 45, 383, 30, fenetrePrincipale, ID_B_QUITTER, instance, NULL);
            return 0;

Comme vous pouvez le voir, les styles WS_CHILD et WS_VISIBLE sont appliqués aux boutons :

  • WS_CHILD solidarise le contrôle à la fenêtre parent. Sans lui, le contrôle flotterait "en l'air", en dehors de la fenêtre.

  • WS_VISIBLE affiche le contrôle (donc, pas besoin de ShowWindow).

Vous pouvez voir alors que les ID sont, comme je l'ai dit pendant la description de cette fonction, à la place du menu normalement associé à une fenêtre.

Si une erreur de type "Invalid conversion from...to..." se présente, effectuez un cast de la variable passée en paramètre en type attendu. En effet, le typage est plus strict en C++ qu'en C.

Les boutons sont maintenant créés, mais n'ont aucune action.

Gestion de l'enfoncement du bouton

Maintenant que les boutons existent, il nous faut les associer à des actions particulières. Tout d'abord, un peu de théorie. Je vous ai dit que le switch du message déterminait le message perçu : celui-ci a pu être dans le cas précédent WM_CREATE, ou WM_DESTROY. Mais ce message est complété par wParam et lParam, passés à la fonction callback. Ceux-ci contiennent des messages complémentaires, ou des notifications. Ces paramètres étant des doubles mots, on peut récupérer le mot bas et le mot haut de chacun de ces deux paramètres grâce aux macros LOWORD() ou HIWORD().

Justement, quand un bouton est enfoncé, le message vaut WM_COMMAND, et le mot bas de wParam détermine l'ID du bouton qui a été cliqué, et qui a donc envoyé ce message. En l'utilisant dans notre code, on obtient donc :

case WM_COMMAND:
    switch(LOWORD(wParam))
    {
       case ID_B_PARLER:
          //Ici l'instruction associée à "Parler"
          break;

       case ID_B_QUITTER:
           //Là l'instruction associée à "Quitter"
           break;
    }
    return 0;

Ainsi, si le bouton "Parler" a été cliqué, le message vaudra WM_COMMAND, et un test du mot bas du paramètre wParam indiquera l'ID de celui ci, ou ID_B_PARLER.

Peut-on, nous (à travers du code), envoyer des messages ?

Ça tombe bien, je voulais l'utiliser pour le bouton "Quitter". :p En effet, ce bouton a le même effet que "la croix". Comme son message associé est WM_DESTROY, nous allons donc envoyer le message WM_DESTROY à la fenêtre principale quand le bouton "Quitter" a été cliqué.

La fonction permettant d'y arriver s'appelle SendMessage (quelle coïncidence ^^) ; son prototype est le suivant :

LRESULT SendMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

Vous l'avez deviné, SendMessage ne fait qu'exécuter la fonction callback avec le message spécifié dans msg, les paramètres spécifiés dans wParam et lParam, et retourne ce que retourne le callback (logique).

Très pratique, vous vous en servirez beaucoup, je vous l'assure ^^ .

En l'adaptant à notre code, nous obtenons ceci :

SendMessage(fenetrePrincipale, WM_DESTROY, 0, 0);

Les paramètres n'étant pas utilisés, nous les mettons à zéro. Ce bout de code est évidemment à mettre dans le ' case ID_B_QUITTER '.

Le bouton "Quitter" est donc opérationnel. Occupons-nous maintenant du bouton "Parler". Ce bouton doit avoir pour effet d'afficher quelque chose à l'utilisateur : par exemple, de l'informer que le bouton a été cliqué (bon d'accord, ce n'est pas très original, mais ce sont les bases :p ). Et pour cela, rien de mieux que MessageBox.

MessageBox

Cette fonction, dont le prototype est le suivant :

int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);

affiche une petite messagebox portant comme titre la chaîne spécifiée par lpCaption, comme texte celui spécifié par lpText, étant rattachée à la fenêtre spécifiée par hWnd et ayant comme style ceux spécifiés dans uType.

Vraiment, vous ne connaissez pas ? Vous avez été bercé aux doux sons des erreurs, ou aux warnings de non-sauvegarde en quittant, tous deux rapportés grâce aux messages boxes. :p

Les icônes et boutons de celles-ci sont déterminés grâce au paramètre uType, mais la liste ne sera pas donnée ici.

Vous trouverez la liste complète dans notre mini-documentation.

Donc, en utilisant cette fonction en guise de réponse à un clic sur "Parler", on obtient ce code :

MessageBox(fenetrePrincipale, "Clic !", "Bonjour.", MB_ICONINFORMATION);

MB_ICONINFORMATION est une constante permettant d'ajouter un bouton 'OK' à la messagebox, et une icône en forme de bulle.
Vous n'avez plus qu'à placer ce code dans le 'case ID_B_PARLER' du callback.

Code complet

Eh bien j'ai l'impression que l'on a fini notre modeste programme ! En voici donc le code complet :

#include <windows.h>

#define ID_B_PARLER 0
#define ID_B_QUITTER 1

HINSTANCE instance;

LRESULT CALLBACK procedureFenetrePrincipale(HWND, UINT, WPARAM, LPARAM);

int WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
LPSTR lignesDeCommande, int modeDAffichage)
{
    HWND fenetrePrincipale;
    MSG message;
    WNDCLASS classeFenetre;

    instance = cetteInstance;

    classeFenetre.style = 0;
    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
    classeFenetre.cbClsExtra = 0;
    classeFenetre.cbWndExtra = 0;
    classeFenetre.hInstance = NULL;
    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    classeFenetre.lpszMenuName =  NULL;
    classeFenetre.lpszClassName = "classeF";

    // On prévoit quand même le cas où ça échoue
    if(!RegisterClass(&classeFenetre)) return FALSE;

    fenetrePrincipale = CreateWindow("classeF", "Ma premiere fenetre winAPI !", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 110,
                                                   NULL, NULL, cetteInstance, NULL);
    if (!fenetrePrincipale) return FALSE;

    ShowWindow(fenetrePrincipale, modeDAffichage);
    UpdateWindow(fenetrePrincipale);

    while (GetMessage(&message, NULL, 0, 0))
    {
        TranslateMessage(&message);
        DispatchMessage(&message);
    }
    return message.wParam;
}

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND boutons[2] = {NULL};

    switch (message)
    {
        case WM_CREATE:
                boutons[0] = CreateWindow("BUTTON", "Parler", WS_CHILD | WS_VISIBLE,
        5, 5, 383, 30, fenetrePrincipale, (HMENU)ID_B_PARLER, instance, NULL);
                boutons[1] = CreateWindow("BUTTON", "Quitter", WS_CHILD | WS_VISIBLE,
        5, 45, 383, 30, fenetrePrincipale, (HMENU)ID_B_QUITTER, instance, NULL);
            return 0;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case ID_B_PARLER:
                    MessageBox(fenetrePrincipale, "Clic", "Bonjour.", MB_ICONINFORMATION);
                    break;

                case ID_B_QUITTER:
                    SendMessage(fenetrePrincipale, WM_DESTROY, 0, 0);
                    break;
            }
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(fenetrePrincipale, message, wParam, lParam);
    }
}

Résultat :

Puis, avec "Parler" :

Rédacteur : Mg++

Les menus

Vous en voulez toujours plus, n'est-ce pas ? ;) Eh bien vous allez apprendre dans ce chapitre à créer des menus. Pour cela, deux manières de coder :

  • en passant par les ressources

  • dynamiquement en passant par des fonctions.

Nécessaire

En premier lieu, reprenez le code du chapitre précédent. En guise de test, nous créerons un menu "Actions" comportant les deux actions présentes sous la forme de boutons "Parler" et "Quitter".

Peu importe la méthode utilisée, des handles de menus seront nécessaires pour chaque menu, et chaque sous-menu associé à la fenêtre. Ceux-ci comportent le type HMENU :

HMENU menu, sousMenu;

Création du menu

En utilisant des fonctions

Les handles de menus peuvent être alors créés en vue d'un remplissage avec la fonction CreateMenu, ne comportant aucun paramètre :

HMENU CreateMenu(VOID);

Une fois créés, nous pouvons les remplir selon leur type avec la fonction suivante :

BOOL AppendMenu(HMENU hMenu, UINT uFlags, UINT_PTR uIDNewItem, LPCSTR lpNewItem);

Cette fonction, dont le rôle est d'ajouter une entrée, que cela soit une commande ou un sous-menu, comporte les arguments suivants :

  • L'argument 1 désigne le menu auquel est associée l'entrée :

    HMENU hMenu

  • L'argument 2 désigne le type de l'entrée, mais aussi par association de flags, des options facultatives (désactivé, coché, illustré, etc.) :

    UINT uFlags

  • L'argument 3 désigne, dans le cas d'une commande, son ID (ayant le même effet que ceux des boutons), ou dans le cas d'une entrée popup, le handle du sous-menu associé :

    UINT_PTR uIDNewItem

  • L'argument 4 désigne le nom affiché par l'entrée :

    LPCSTR lpNewItem

Si vous voulez créer une entrée popup (donc un sous-menu), le uFlags doit correspondre à la valeur MF_POPUP. Dans le cas d'une commande, il doit être égal à MF_STRING.
Les séparateurs peuvent, eux, être créés avec MF_SEPARATOR (les deux derniers paramètres seront alors ignorés).

Bien sûr, les sous-menus doivent être définis avant d'être associés à un menu, ou, dans le cas contraire, celui-ci n'aurait rien à intégrer.

Dans notre cas, la création du menu serait comme ceci :

//Déclarations :
HMENU menu, sousMenu;

//...

sousMenu = CreateMenu();
AppendMenu(sousMenu, MF_STRING, ID_B_PARLER, "Parler");
AppendMenu(sousMenu, MF_SEPARATOR, (UINT)NULL, "");
AppendMenu(sousMenu, MF_STRING, ID_B_QUITTER, "Quitter");

menu = CreateMenu();
AppendMenu(menu, MF_POPUP, (UINT)sousMenu, "Actions");
En utilisant les ressources

Les ressources servent à énormément de choses en WinAPI. Elles servent, comme vous le savez, à spécifier les fichiers stockés dans l'exécutable en précisant son ID, type et chemin, mais aussi à définir des accélérateurs clavier (que nous verrons plus tard), des schémas de boîtes de dialogue (dont nous aborderons l'utilisation dans le chapitre suivant), ou encore des schémas de menus (qui nous intéressent ici :p ), et bien d'autres choses.

La création d'un menu en ressource est plus logique qu'en utilisant AppendMenu. Elle observe donc une hiérarchie sous cette forme, qu'on appelle communément template :

ID_MENU MENU
BEGIN
    POPUP "Menu"
    BEGIN
        POPUP "Sous Menu"
        BEGIN
                MENUITEM "Truc", ID_TRUC
                MENUITEM SEPARATOR
                MENUITEM "Machin", ID_MACHIN
        END
        MENUITEM "Groar", ID_GROAR
    END
    POPUP "Menu2"
    BEGIN
        MENUITEM "Miaou", ID_MIAOU
    END
END

(Bon d'accord, vive les noms... mais c'était pour vous montrer les différents cas possibles :-° .)

Vous ne connaissez sûrement pas les mots-clés BEGIN et END : en fait, il s'agit d'une autre notation utilisée en ressources pour les accolades ouvrantes et fermantes.

Comme vous pouvez le voir, ID_MENU n'est pas visible : il s'agit de l'identificateur du menu. (D'ailleurs, bien qu'il soit un ID, il n'a pas besoin d'être défini.) Seules les entrées entre ses BEGIN et END sont visibles : POPUP désigne les menus déroulants, et MENUITEM une commande (je suppose que vous savez ce que signifie SEPARATOR désormais ;) ). Les ID de chaque commande doivent être spécifiés après le nom affiché et une virgule.

On peut, tout comme AppendMenu, lui appliquer des flags permettant de cocher l'entrée, la désactiver, etc. ; dans ce cas-ci, elles sont spécifiées à la suite après l'ID de la commande et une virgule séparatrice.

Pour notre exemple, le code en ressource serait le suivant :

ID_MENU MENU
BEGIN
        POPUP "Actions"
        BEGIN
                MENUITEM "Parler", ID_B_PARLER
                MENUITEM "Quitter", ID_B_QUITTER
        END
END

Bien. Après avoir créé le menu, encore faut-il le récupérer pour l'associer à la fenêtre. Pour cela, on utilise LoadMenu. Cette fonction, prenant comme premier paramètre l'instance actuelle, et en deuxième l'ID du menu, se charge de renvoyer un handle de menu. En utilisant ce handle de menu, vous pourrez l'utiliser lors de CreateWindow ou SetMenu (voir méthodes d'associations ci-après).

Si vous utilisez la classe de fenêtre pour l'association, vous pouvez vous passer de ce passage, et indiquer de suite l'ID du menu au champ de la classe correspondante.

Association du menu avec la fenêtre

Afin d'intégrer le menu à la fenêtre, vous avez trois méthodes.

  • Dans le cas d'une définition sans ressources (ou avec, mais en utilisant LoadMenu), à la création de la fenêtre, vous devez passer le nom du menu parent (ici menu) au paramètre n°9 de CreateWindow, étant hMenu, sans guillemets. Exemple :

    fenetrePrincipale = CreateWindow("classeF", "Ma premiere fenetre winAPI !", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 130, NULL, <gras>menu</gras>, cetteInstance, NULL);

  • Dans le cas d'une définition utilisant les ressources, à la définition de la classe de fenêtre, vous devez passer le nom identificateur du menu au paramètre lpszMenuName de la structure WNDCLASS entre guillemets. Exemple :

    classeFenetre.lpszMenuName = "MENU";

  • Dans les deux cas, dans du reste du code, vous pouvez utiliser la fonction SetMenu, prenant comme premier paramètre le nom de la fenêtre à laquelle le menu (précisé dans le paramètre 2 sous HMENU) est associé. Exemple (cas utilisant les ressources) :

    SetMenu(fenetrePrincipale, LoadMenu(instance, "ID_MENU"));

Code complet

Contenu des autres fichiers (cas des ressources)

Resource.rc :

#include <windows.h>

#include "constantes.h"

ID_MENU MENU
BEGIN
    POPUP "Actions"
    BEGIN
        MENUITEM "Parler", ID_B_PARLER
        MENUITEM SEPARATOR
        MENUITEM "Quitter", ID_B_QUITTER
    END
END

constantes.h :

#ifndef CONSTANTES_H
#define CONSTANTES_H

#define ID_B_PARLER 0
#define ID_B_QUITTER 1

#endif

(Protection contre les inclusions multiples facultative pour le moment.)

1ère méthode
#include <windows.h>

#define ID_B_PARLER 0
#define ID_B_QUITTER 1

HINSTANCE instance;

LRESULT CALLBACK procedureFenetrePrincipale(HWND, UINT, WPARAM, LPARAM);

int WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
LPSTR lignesDeCommande, int modeDAffichage)
{
    HWND fenetrePrincipale;
    MSG message;
    WNDCLASS classeFenetre;
    HMENU menu, sousMenu;

    instance = cetteInstance;

    sousMenu = CreateMenu();
    AppendMenu(sousMenu,MF_STRING, ID_B_PARLER, "Parler");
    AppendMenu(sousMenu,MF_SEPARATOR, (UINT)NULL, "");
    AppendMenu(sousMenu,MF_STRING,ID_B_QUITTER,"Quitter");

    menu = CreateMenu();
    AppendMenu(menu, MF_POPUP, (UINT)sousMenu, "Actions");

    classeFenetre.style = 0;
    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
    classeFenetre.cbClsExtra = 0;
    classeFenetre.cbWndExtra = 0;
    classeFenetre.hInstance = NULL;
    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    classeFenetre.lpszMenuName = NULL;
    classeFenetre.lpszClassName = "classeF";

    // On prévoit quand même le cas où ça échoue
    if(!RegisterClass(&classeFenetre)) return FALSE;

    fenetrePrincipale = CreateWindow("classeF", "Ma premiere fenetre winAPI !", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 130, NULL, menu /* Ou LoadMenu(instance, "ID_MENU")*/, cetteInstance, NULL);

    if (!fenetrePrincipale) return FALSE;

    ShowWindow(fenetrePrincipale, modeDAffichage);
    UpdateWindow(fenetrePrincipale);

    while (GetMessage(&message, NULL, 0, 0))
    {
        TranslateMessage(&message);
        DispatchMessage(&message);
    }
    return message.wParam;
}

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND boutons[2] = {NULL};

    switch (message)
    {
        case WM_CREATE:
                boutons[0] = CreateWindow("BUTTON", "Parler", WS_CHILD | WS_VISIBLE,
        5, 5, 383, 30, fenetrePrincipale, (HMENU)ID_B_PARLER, instance, NULL);
                boutons[1] = CreateWindow("BUTTON", "Quitter", WS_CHILD | WS_VISIBLE,
        5, 45, 383, 30, fenetrePrincipale, (HMENU)ID_B_QUITTER, instance, NULL);
            return 0;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case ID_B_PARLER:
                    MessageBox(fenetrePrincipale, "Clic", "Bonjour.", MB_ICONINFORMATION);
                    break;

                case ID_B_QUITTER:
                    SendMessage(fenetrePrincipale, WM_DESTROY, 0, 0);
                    break;
            }
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(fenetrePrincipale, message, wParam, lParam);
    }
}
2ème méthode
#include <windows.h>

#include "constantes.h"

HINSTANCE instance;

LRESULT CALLBACK procedureFenetrePrincipale(HWND, UINT, WPARAM, LPARAM);

int WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
LPSTR lignesDeCommande, int modeDAffichage)
{
    HWND fenetrePrincipale;
    MSG message;
    WNDCLASS classeFenetre;

    instance = cetteInstance;

    classeFenetre.style = 0;
    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
    classeFenetre.cbClsExtra = 0;
    classeFenetre.cbWndExtra = 0;
    classeFenetre.hInstance = NULL;
    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    classeFenetre.lpszMenuName = "ID_MENU";
    classeFenetre.lpszClassName = "classeF";

    // On prévoit quand même le cas où ça échoue
    if(!RegisterClass(&classeFenetre)) return FALSE;

    fenetrePrincipale = CreateWindow("classeF", "Ma premiere fenetre winAPI !", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 130, NULL, NULL, cetteInstance, NULL);

    if (!fenetrePrincipale) return FALSE;

    ShowWindow(fenetrePrincipale, modeDAffichage);
    UpdateWindow(fenetrePrincipale);

    while (GetMessage(&message, NULL, 0, 0))
    {
        TranslateMessage(&message);
        DispatchMessage(&message);
    }
    return message.wParam;
}

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND boutons[2] = {NULL};

    switch (message)
    {
        case WM_CREATE:
                boutons[0] = CreateWindow("BUTTON", "Parler", WS_CHILD | WS_VISIBLE,
        5, 5, 383, 30, fenetrePrincipale, (HMENU)ID_B_PARLER, instance, NULL);
                boutons[1] = CreateWindow("BUTTON", "Quitter", WS_CHILD | WS_VISIBLE,
        5, 45, 383, 30, fenetrePrincipale, (HMENU)ID_B_QUITTER, instance, NULL);
            return 0;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case ID_B_PARLER:
                    MessageBox(fenetrePrincipale, "Clic", "Bonjour.", MB_ICONINFORMATION);
                    break;

                case ID_B_QUITTER:
                    SendMessage(fenetrePrincipale, WM_DESTROY, 0, 0);
                    break;
            }
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(fenetrePrincipale, message, wParam, lParam);
    }
}
3ème méthode
#include <windows.h>

#include "constantes.h"

HINSTANCE instance;

LRESULT CALLBACK procedureFenetrePrincipale(HWND, UINT, WPARAM, LPARAM);

int WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
LPSTR lignesDeCommande, int modeDAffichage)
{
    HWND fenetrePrincipale;
    MSG message;
    WNDCLASS classeFenetre;

    instance = cetteInstance;

    classeFenetre.style = 0;
    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
    classeFenetre.cbClsExtra = 0;
    classeFenetre.cbWndExtra = 0;
    classeFenetre.hInstance = NULL;
    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    classeFenetre.lpszMenuName = NULL;
    classeFenetre.lpszClassName = "classeF";

    // On prévoit quand même le cas où ça échoue
    if(!RegisterClass(&classeFenetre)) return FALSE;

    fenetrePrincipale = CreateWindow("classeF", "Ma premiere fenetre winAPI !", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 130, NULL, NULL, cetteInstance, NULL);
    if (!fenetrePrincipale) return FALSE;

    ShowWindow(fenetrePrincipale, modeDAffichage);
    UpdateWindow(fenetrePrincipale);

    while (GetMessage(&message, NULL, 0, 0))
    {
        TranslateMessage(&message);
        DispatchMessage(&message);
    }
    return message.wParam;
}

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND boutons[2] = {NULL};

    switch (message)
    {
        case WM_CREATE:
                boutons[0] = CreateWindow("BUTTON", "Parler", WS_CHILD | WS_VISIBLE,
        5, 5, 383, 30, fenetrePrincipale, (HMENU)ID_B_PARLER, instance, NULL);
                boutons[1] = CreateWindow("BUTTON", "Quitter", WS_CHILD | WS_VISIBLE,
        5, 45, 383, 30, fenetrePrincipale, (HMENU)ID_B_QUITTER, instance, NULL);
                SetMenu(fenetrePrincipale, LoadMenu(instance, "ID_MENU"));
                /*Ou dans le cas manuel, SetMenu(fenetrePrincipale, menu); où menu a été d"jà créé*/
            return 0;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case ID_B_PARLER:
                    MessageBox(fenetrePrincipale, "Clic", "Bonjour.", MB_ICONINFORMATION);
                    break;

                case ID_B_QUITTER:
                    SendMessage(fenetrePrincipale, WM_DESTROY, 0, 0);
                    break;
            }
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(fenetrePrincipale, message, wParam, lParam);
    }
}

Résultat :

Pour aller plus loin

Activer / désactiver une entrée

  • Dès la création du menu : spécifier le flag MF_DISABLED pour désactiver l'entrée (MF_ENABLED, permettant de l'activer, étant par défaut), ou MF_GRAYED pour le griser. Exemple :

    MENUITEM "Parler", ID_B_PARLER, MF_GRAYED

  • Dans le reste du code : utiliser la fonction EnableMenuItem, prenant comme premier paramètre le handle de menu, comme deuxième soit l'emplacement (basé sur 0) de l'entrée, soit sa commande (le choix étant spécifié par la valeur du troisième paramètre), et comme troisième son mode, par combinaison de son mode de détermination (MF_BYCOMMAND implique d'utiliser la commande pour le deuxième paramètre, et MF_BYPOSITION l'inverse) et du mode d'affichage, étant les mêmes valeurs possibles que pour les flags des entrées décrits ci-dessus. Exemple :

    EnableMenuItem(menu, ID_B_PARLER, MF_BYCOMMAND | MF_GRAYED);
Cocher / Décocher une entrée

  • Dès la création du menu, on peut spécifier le flag MF_CHECKED (pour coché) ou MF_UNCHECKED (valeur par défaut, non coché). Exemple :

    MENUITEM "Parler", ID_B_PARLER, MF_CHECKED

  • Dans le reste du code : utiliser la fonction CheckMenuItem, prenant comme premier paramètre le handle de menu concerné, et pour les deuxième et troisième paramètres, le même système que pour EnableMenuItem, excepté le fait que les états possibles sont ceux spécifiés ci-dessus. Exemple :

    CheckMenuItem(menu, ID_B_PARLER, MF_BYCOMMAND | MF_CHECKED);

MF_BYCOMMAND permet d'utiliser la commande de l'élément de menu concerné comme moyen de le déterminer, mais rien ne vous empêche d'utiliser MF_BYPOSITION pour son index.

Ajouter une icône à un élément de menu

Utilisez SetMenuItemBitmaps(), comme illustré dans l'exemple suivant :

SetMenuItemBitmaps(
                      hMenu,
                      ID_PARLER,
                      MF_BYCOMMAND,
                      LoadBitmap(hInstance, "talkBtnBmp_normal"),
                      LoadBitmap(hInstance, "talkBtnBmp_selected")
                  );

Rédacteur : Mg++

Les boîtes de dialogue

Nous allons maintenant aborder les boîtes de dialogue. Pour ce chapitre, l'objectif sera juste d'ajouter une boîte de dialogue simple (comportant une icône et du texte) faisant office d'"A propos".

Préparation

Pour faire apparaître la boîte de dialogue, nous avons besoin d'une commande. Pour cela, nous allons rajouter un menu popup, au même niveau que "Actions", mais ayant pour nom "Aide". Dans celui-ci, une entrée-commande sera ajoutée, portant "A propos" comme nom, et ID_B_APROPOS comme ID.

Pour cela, ajoutez (exemple utilisant les ressources : adaptez en conséquence pour les autres méthodes) :

POPUP "Aide"
BEGIN
        MENUITEM "A propos", ID_B_APROPOS
END

dans le template du menu en ressources.

ID_B_APROPOS devra être par ailleurs défini dans constantes.h.

Enfin et bien sûr, ajoutez un nouveau 'case' portant cet ID dans le switch du mot faible du message WM_COMMAND. Nous le remplirons plus tard.

La création d'une boîte de dialogue exige deux parties distinctes : la définition de son template, et sa création proprement dite. Voyons chacune de ces étapes.

Template ressource

Vous vous souvenez du template d'un menu en ressources ? C'est à peu près pareil pour une boîte de dialogue, excepté le fait que le mot-clé MENU est remplacé par DIALOG (ou DIALOGEX, si vous utilisez des styles étendus), et des informations supplémentaires comme sa position / largeur / hauteur, ses styles et son titre sont rajoutés juste avant la définition. Un schéma basique de boîte de dialogue peut alors être le suivant :

ID_DIALOGUE DIALOG
    CW_USEDEFAULT, CW_USEDEFAULT, 200, 120
    STYLE WS_OVERLAPPEDWINDOW
        CAPTION "Titre"
BEGIN
END

L'ordre des ajouts n'est pas important, excepté le fait que les positions (les quatre paramètres entre virgules) de la boîte de dialogue doivent être les premiers à être définis.
WS_OVERLAPPEDWINDOW rend la boîte de dialogue modale, parfaitement banale : barre de titre, menu système (réduire, fermer, etc.)... tous les styles applicables aux fenêtres principales (contenus donc dans le champ uStyle de la WNDCLASS) le sont ici. Quant à CAPTION (dont la traduction française donne "Titre"), il indique la chaîne de caractères à afficher dans la barre de titre (d'où son nom).

Vous pouvez consulter tous les styles applicables aux boîtes de dialogue dans la documentation ci-jointe.

Chaque ajout de contrôle s'exécute par l'intermédiaire de mots-clés. Sauf exception (informations en surplus nécessaires, etc.), les lignes de "déclaration" de contrôles en utilisant ces mots-clés possèdent une organisation de ce type :

MOTCLE TEXTE_ASSOCIE, ID, x, y, w, h, STYLES

Où x et y sont les coordonnées du coin supérieur gauche du contrôle, et w et h les dimensions du contrôle.

Par exemple, un bouton possède comme mot clé PUSHBUTTON. S'il est le bouton par défaut, il est alors DEFPUSHBUTTON.
Autre exemple (je ne les choisis pas au hasard ;) ) : une icône possède le même mot-clé que pendant une intégration à l'exécutable de celle-ci, en passant par les ressources, à savoir ICON.
Enfin, du texte peut être affiché avec le mot clé LTEXT (justifié à gauche) ou RTEXT (justifié à droite).

D'autres mots-clés seront présentés en même temps que le cours sur le contrôle concerné (prochaine partie).

Maintenant que vous savez tout ceci, vous êtes prêts à définir le template de votre boîte de dialogue. En premier lieu, intégrez à l'exécutable l'icône voulue avec, comme vous le savez, la syntaxe suivante :

icone1 ICON "chemin/icone.ico"

Je vous propose donc :

APROPOS DIALOG
    CW_USEDEFAULT, CW_USEDEFAULT, 200, 120
        CAPTION "Titre"
        STYLE WS_OVERLAPPEDWINDOW
BEGIN
    DEFPUSHBUTTON "Ok", IDOK, 96, 90, 42, 12
    ICON icone1, -1, 60, 55, 32, 32
    LTEXT "A propos test des boîtes de dialogue", -1, 100, 58, 100, 10
END

IDOK est déjà déclaré avec l'API, tout comme IDCANCEL et d'autres.

-1 est spécifié quand le contrôle n'a pas besoin d'ID (comme les icônes ou ici les textes).

Maintenant que le schéma est créé, passons à la création proprement dite.

Création

Pour créer la boîte de dialogue, deux méthodes : soit vous en faites une indépendante, de déco, etc., soit vous voulez la manipuler.

Les fonctions utilisées auront chacune un but spécifique, mais rien ne vous empêche d'utiliser l'une ou l'autre tout le temps. Ce sont juste des questions de goûts et de besoins.

Vous pouvez donc utiliser soit CreateDialog, soit DialogBox. Leur prototype est presque pareil : elles possèdent toutes deux les mêmes paramètres, mais CreateDialog permet de récupérer un handle de fenêtre sur la boîte de dialogue créée (ce qui vous fait donc deviner que CreateDialog est la fonction à utiliser, si vous voulez manipuler la boîte de dialogue à partir d'une autre fenêtre, récupérer des infos, etc.).
Voici donc leurs paramètres :

  • Le premier désigne l'instance actuelle :

    HINSTANCE hInstance

  • Le deuxième désigne le template à utiliser, donc l'ID (plutôt une chaîne identifiante) de la boîte de dialogue entre guillemets :

    LPCTSTR lpTemplate

  • Le troisième désigne le handle de la fenêtre parent :

    HWND hWndParent

  • Le dernier désigne la fonction callback de la boîte de dialogue (un cast sera peut être nécessaire) :

    DLGPROC lpDialogFunc

L'intérêt de CreateDialog subsiste dans le fait que la boîte de dialogue peut être créée au tout début, être manipulée entre temps et affichée quand on la demande. Mais tout comme DialogBox, elle s'affiche juste après être créée. Vous allez donc peut être avoir recours à ShowWindow.

Vous pouvez aussi passer des paramètres aux boîtes de dialogue créées, en utilisant DialogBoxParam ou CreateDialogParam permettant d'envoyer une information (structure) dans le paramètre lParam du message WM_INITDIALOG (celui-ci étant donc passé dans le 5ème paramètre de ces deux fonctions).

Donc, si on choisit par exemple DialogBox comme fonction, on aura alors l'extrait de code suivant :

case WM_COMMAND:
  switch(LOWORD(wParam))
  {
      case ID_B_APROPOS:
         DialogBox(instance, "APROPOS", fenetrePrincipale, (DLGPROC)aPropos_procedure);
         break;

Or, aPropos_procedure n'existe pas. Nous allons donc nous occuper de sa fonction callback.

La fonction callback

Similaire à la procédure de la fenêtre principale, elle possède néanmoins un type de retour différent : BOOL APIENTRY. Je ne vais pas non plus le décrire ici.

Quelques différences notoires : WM_CREATE a été remplacé par WM_INITDIALOG, et WM_DESTROY n'est pas envoyé : EndDialog s'occupe de fermer la fenêtre (donc à ne pas utiliser de suite avec CreateDialog) en prenant comme paramètre : le handle de la boîte de dialogue, et 0.

On peut donc avoir la procédure suivante :

BOOL APIENTRY aPropos_procedure(HWND boiteDeDialogue,UINT message,WPARAM wParam,LPARAM lParam)
{
    switch (message)
    {
      case WM_INITDIALOG:

         return TRUE;

      case WM_COMMAND:
         if (LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK)
         {
           EndDialog(boiteDeDialogue,0);
           return TRUE;
         }
         return 0;

      default:
         return FALSE;
    }
}

Votre boîte de dialogue est alors achevée !

Code complet

Voici maintenant le code complet :

ressource.rc :

#include <windows.h>

#include "constantes.h"

icone1 ICON "Icones/icone.ico"

ID_MENU MENU
BEGIN
    POPUP "Actions"
    BEGIN
        MENUITEM "Parler", ID_B_PARLER
        MENUITEM SEPARATOR
        MENUITEM "Quitter", ID_B_QUITTER
    END
    POPUP "Aide"
    BEGIN
        MENUITEM "A propos", ID_B_APROPOS
    END
END

APROPOS DIALOG
    CW_USEDEFAULT, CW_USEDEFAULT, 200, 120
        CAPTION "Titre"
        STYLE WS_OVERLAPPEDWINDOW
BEGIN
    DEFPUSHBUTTON "Ok", IDOK, 76, 90, 42, 12
    ICON icone1, -1, 30, 50, 32, 32
    LTEXT "A propos test des boîtes de dialogue", -1, 60, 38, 200, 10
END

constantes.h :

#ifndef CONSTANTES_H
#define CONSTANTES_H

#define ID_B_PARLER 0
#define ID_B_QUITTER 1
#define ID_B_APROPOS 2

#endif

main.cpp :

#include <windows.h>

#include "constantes.h"

HINSTANCE instance;

LRESULT CALLBACK procedureFenetrePrincipale(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY aPropos_procedure(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam);

int WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
LPSTR lignesDeCommande, int modeDAffichage)
{
    HWND fenetrePrincipale;
    MSG message;
    WNDCLASS classeFenetre;

    instance = cetteInstance;

    classeFenetre.style = 0;
    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
    classeFenetre.cbClsExtra = 0;
    classeFenetre.cbWndExtra = 0;
    classeFenetre.hInstance = NULL;
    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    classeFenetre.lpszMenuName = NULL;
    classeFenetre.lpszClassName = "classeF";

    // On prévoit quand même le cas où ça échoue
    if(!RegisterClass(&classeFenetre)) return FALSE;

    fenetrePrincipale = CreateWindow("classeF", "Ma premiere fenetre winAPI !", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 130,
                                                   NULL, LoadMenu(instance, "ID_MENU"), cetteInstance, NULL);
    if (!fenetrePrincipale) return FALSE;

    ShowWindow(fenetrePrincipale, modeDAffichage);
    UpdateWindow(fenetrePrincipale);

    while (GetMessage(&message, NULL, 0, 0))
    {
        TranslateMessage(&message);
        DispatchMessage(&message);
    }
    return message.wParam;
}

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND boutons[2] = {NULL};

    switch (message)
    {
        case WM_CREATE:
                boutons[0] = CreateWindow("BUTTON", "Parler", WS_CHILD | WS_VISIBLE,
        5, 5, 383, 30, fenetrePrincipale, (HMENU)ID_B_PARLER, instance, NULL);
                boutons[1] = CreateWindow("BUTTON", "Quitter", WS_CHILD | WS_VISIBLE,
        5, 45, 383, 30, fenetrePrincipale, (HMENU)ID_B_QUITTER, instance, NULL);
            return 0;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case ID_B_APROPOS:
                    DialogBox(instance, "APROPOS", fenetrePrincipale, (DLGPROC)aPropos_procedure);
                    break;

                case ID_B_PARLER:
                    MessageBox(fenetrePrincipale, "Clic", "Bonjour.", MB_ICONINFORMATION);
                    break;

                case ID_B_QUITTER:
                    SendMessage(fenetrePrincipale, WM_DESTROY, 0, 0);
                    break;
            }
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(fenetrePrincipale, message, wParam, lParam);
    }
}

BOOL APIENTRY aPropos_procedure(HWND boiteDeDialogue,UINT message,WPARAM wParam,LPARAM lParam)
{
    switch (message)
    {
      case WM_INITDIALOG:

         return TRUE;

      case WM_COMMAND:
         if (LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK)
         {
           EndDialog(boiteDeDialogue,0);
           return TRUE;
         }
         return 0;

      default:
         return FALSE;
    }
}

Résultat : (pas d'icône chez moi, je n'en avais pas sous la main ;) )

et

Pour aller plus loin

  • Vous pouvez récupérer le handle d'un contrôle d'une boîte de dialogue (pour pouvoir lui envoyer des messages) avec GetDlgItem, prenant comme premier paramètre le handle de la boîte de dialogue parent du contrôle, et en deuxième son ID.

  • Vous pouvez changer dynamiquement le texte des XTEXT avec SetDlgItemText, (ou Int, rajoutant un quatrième paramètre étant un booléen si l'int est signé ou non) prenant les mêmes paramètres que GetDlgItem, mais avec en plus le texte à afficher.

  • A l'inverse, vous pouvez utiliser GetDlgItemText (ou Int, où le troisième paramètre est un pointeur d'int qui va récupérer les éventuelles erreurs, et un quatrième spécifiant si l'int est signé ou non. La valeur est alors retournée.) pour en récupérer la valeur. Le troisième paramètre est alors une variable qui va contenir la valeur, et un quatrième est ajouté pour spécifier la taille de la chaîne à récupérer (pour éviter les dépassements de tampon).

Vous avez maintenant acquis toutes les bases (de chez base :p ), et pourrez alors étendre vos connaissances avec le prochain chapitre, lequel portera sur les différents contrôles.

Rédacteur : Mg++

Apprentissage de l'API Windows Les contrôles
Grandes lignes
  1. Les bases
  2. Les contrôles
  3. L'affichage
  4. TP : Un lecteur audio
  5. Les événements
  6. Esthétique avancée

L'auteur

pasquestion etpuisquoiencore


Cours sur L'API Winodos de openclassrooms

Les contrôles

Vous en savez maintenant assez pour pouvoir utiliser les autres contrôles, qui vous sont expliqués ici :magicien: :

&lt;information&gt;Seuls les points importants sont abordés. Si vous ne trouvez pas votre bonheur, je vous invite à aller sur msdn.&lt;/information&gt;

Les boutons (tous types)

Ce contrôle est l'un des plus utiles, mais aussi le plus complet.
En effet, ce que l'on désigne par BUTTON est bien plus qu'un bouton, ce peut être :

  • Un "push button" :

  • Une "check box" (deux ou trois états) :

  • Un "radio button" :

  • Une "group box" :

Nous allons détailler tour à tour chaque utilisation.

Dans la suite de la description, j'emploierai les noms anglais ci-dessus.

  • La première utilisation (la plus classique) est sans contestation celle du bouton poussoir, qui s'enclenche quand vous cliquez dessus.

  • La seconde est tout aussi utile : c'est une case à cocher. Elle peut avoir 2 ou 3 états. Les deux états communs sont coché, et non coché. Le troisième est non assigné, c'est-à-dire que vous ne voulez pas faire de choix entre les deux.

  • La troisième permet de faire un choix entre plusieurs possibilités.

  • La quatrième permet de regrouper les contrôles dans un carré surmonté d'un nom.

Création

Pour créer un BUTTON, il faut appeler CreateWindow en spécifiant "BUTTON" comme classe, ou utiliser le mot-clé PUSHBUTTON en ressource. Néanmoins, cela ne suffit pas car, comme vous l'avez vu, il y a plusieurs possibilités. Ce qui va nous intéresser est donc l'attribut de style. En plus de spécifier les styles habituels (WM_CHILD, WM_VISIBLE), vous pouvez spécifier un ou plusieurs de ces styles :

  • BS_3STATE : check box à 3 états.

  • BS_AUTO3STATE : check box trois états, qui bascule automatiquement d'un état à l'autre.

  • BS_AUTOCHECKBOX : idem, mais en 2 états.

  • BS_AUTORADIOBUTTON : radio button qui bascule automatiquement d'un choix à l'autre.

  • BS_CHECKBOX : check box deux états.

  • BS_DEFPUSHBUTTON : push button par défaut de la fenêtre.

  • BS_GROUPBOX : group box.

  • BS_LEFTTEXT : affiche le texte à gauche du radio button ou de la check box.

  • BS_OWNERDRAW : vous permet de dessiner vous-même le contrôle.

  • BS_PUSHBUTTON : push button.

  • BS_RADIOBUTTON : radio button.

  • BS_USERBUTTON : obsolète.

  • BS_BITMAP : affiche une image.

  • BS_BOTTOM : affiche le texte en bas du bouton.

  • BS_CENTER : affiche le texte au milieu du bouton (horizontalement).

  • BS_ICON : affiche une icône.

  • BS_FLAT : style "plat" (pas d'effet de profondeur).

  • BS_LEFT : affiche le texte à gauche du bouton.

  • BS_MULTILINE : si le texte est trop grand, le contrôle l'affichera sur plusieurs lignes.

  • BS_NOTIFY : permet au contrôle d'envoyer des notifications de type BN_KILLFOCUS et BN_SETFOCUS.

  • BS_PUSHLIKE : fait apparaître et comporter le contrôle comme un bouton (que ce soit un check box ou radio button).

  • BS_RIGHT : affiche le texte à droite du bouton.

  • BS_RIGHTBUTTON : même chose que BS_LEFTTEXT.

  • BS_TEXT : affiche du texte.

  • BS_TOP : affiche le texte en haut du bouton.

  • BS_VCENTER : affiche le texte au milieu du bouton, et verticalement.

Vous avez sûrement remarqué qu'il existait le style automatique et non-automatique pour les check box et radio button. Ceux-ci déterminent si vous voulez que le contrôle change lui-même d'état quand on clique dessus, ou si vous voulez vous en charger vous-mêmes. On préférera le plus souvent le style automatique.

Comment déterminer les radio button qui fonctionnent ensemble ?

C'est très simple : à chaque fois que vous commencez un nouveau groupe de radio button fonctionnant ensemble, il vous faut ajouter le style WS_GROUP lors de la création avec CreateWindow (voir le code complet en fin de description).

Il est évident que vous ne devez pas combiner des styles contradictoires, comme le texte affiché à droite et à gauche, ou le style check box et push button en même temps...

Notifications

Je vais décrire ici les notifications envoyées par le contrôle, via le message WM_COMMAND (sauf mention contraire). Nous partirons du principe que vous savez comment interagir avec ce message, et je décrirai donc ici uniquement les codes de notifications.

Bon, pour ceux qui n'ont pas suivi (vous savez, au fond de la salle, près du radiateur) on rappelle tout de même que l'ID du contrôle à l'origine de la notification ainsi que ladite notification constituent respectivement le mot bas et le mot haut de WM_COMMAND (abusez des macros LOWORD() et HIWORD()...)

.

BN_CLICKED

Cette notification est envoyée quand l'utilisateur clique sur un bouton.

BN_DBLCLK ou BN_DOUBLECLICKED

Cette notification est envoyée quand l'utilisateur double-clique sur un bouton.

Les boutons de type BS_RADIOBUTTON et BS_OWNERDRAW envoient ce message automatiquement.
Les autres ne l'envoient que si le style BS_NOTIFY est spécifié dans leurs styles.

WM_CTLCOLORBTN

Ce message est envoyé à la fenêtre parent juste avant que le contrôle ne soit dessiné.
Il est utile principalement pour changer la couleur du texte, ou du fond du contrôle pour le dessin.

Les paramètres valent alors:

  • wParam : handle du DC sur lequel dessiner.

  • lParam : handle du bouton.

  • Valeur de retour : handle d'une brosse qui sera utilisée pour le dessin du fond.

Ce message est envoyé directement à la fonction de gestion des messages, et non pas via le message WM_COMMAND.
La brosse retournée ne sera pas automatiquement détruite par le système.

Messages

Ces messages peuvent être envoyés au contrôle avec la fonction :

SendMessage(hCtl,iCode,wParam,lParam)

Pour chaque message, je décrirai le rôle et la signification de wParam et lParam, ainsi que la valeur retournée.

BCM_GETIDEALSIZE

Ce message permet de récupérer la taille du bouton permettant d'afficher correctement le texte et l'image (si présente).

  • wParam : 0.

  • lParam : pointeur vers une structure de type SIZE qui recevra la taille désirée.

  • Valeur de retour : TRUE en cas de succès, et FALSE en cas d'échec.

BM_CLICK

Ce message permet de simuler un clic de souris sur le bouton.

  • wParam : 0.

  • lParam : 0.

  • Valeur de retour : aucune.

BM_GETCHECK

Ce message permet de récupérer l'état d'un check box ou d'un radio button.

  • wParam : 0.

  • lParam : 0.

  • Valeur de retour : BST_CHECKED, BST_INDETERMINATE ou BST_UNCHECKED (ou 0 en cas d'échec).

BM_GETIMAGE

Ce message permet de récupérer le handle de l'image, ou de l'icône associée au bouton.

  • wParam : IMAGE_BITMAP ou IMAGE_ICON.

  • lParam : 0.

  • Valeur de retour : le handle de l'image ou de l'icône (ou NULL en cas d'échec).

BM_GETSTATE

Ce message permet de récupérer l'état d'un bouton ou d'une check box.

  • wParam : 0.

  • lParam : 0.

  • Valeur de retour : c'est la combinaison d'une ou plusieurs de ces valeurs :

    • BST_CHECKED : le bouton est coché.

    • BST_FOCUS : le bouton a le focus clavier.

    • BST_INDETERMINATE : la check box est non assignée.

    • BST_PUSHED : le bouton est maintenu enfoncé.

    • BST_UNCHECKED : le bouton n'est pas coché.

BM_SETCHECK

Ce message permet de définir l'état d'une check box ou un d'un radio button.

  • wParam : BST_CHECKED, BST_INDETERMINATE ou BST_UNCHECKED

  • lParam : 0.

  • Valeur de retour: 0.

BM_SETIMAGE

Ce message permet de définir le handle de l'image, ou de l'icône associée au bouton.

  • wParam : IMAGE_BITMAP ou IMAGE_ICON.

  • lParam : le handle de l'image ou de l'icône.

  • Valeur de retour : le handle de l'image, ou de l'icône associée précédente (ou NULL si aucune).

BM_SETSTATE

Ce message permet de définir l'état de surbrillance d'un bouton. L'état de surbrillance est utilisé lorsque l'utilisateur est en train de cliquer sur le bouton.

  • wParam : TRUE pour mettre le bouton en surbrillance et FALSE pour ne pas le mettre en surbrillance.

  • lParam : 0.

  • Valeur de retour : 0.

L'état de surbrillance n'affecte pas l'état même d'un bouton, à savoir enfoncé ou pas (ou coché / non coché pour une check box et radio button). Il affecte uniquement le dessin du bouton.

Exemple

Ceci est le code complet d'un exemple utilisant les boutons.

#include <windows.h>
#include <stdio.h>

HINSTANCE instance;

#define ID_PUSHBUTTON_1     110
#define ID_PUSHBUTTON_2     111
#define ID_PUSHBUTTON_3     112
#define ID_CHECKBOX_1       113
#define ID_CHECKBOX_2       114
#define ID_CHECKBOX_3       115
#define ID_CHECKBOX_4       116
#define ID_RADIOBUTTON_11   117
#define ID_RADIOBUTTON_12   118
#define ID_RADIOBUTTON_13   119
#define ID_RADIOBUTTON_21   120
#define ID_RADIOBUTTON_22   121
#define ID_RADIOBUTTON_23   122

VOID RemplieFenetrePrincipale(HWND fenetrePrincipale)
{
    // Premier groupe: le push button
    HWND hGroup=CreateWindow(
        "BUTTON",
        "Le push button",
        WS_CHILD|WS_VISIBLE|BS_GROUPBOX,
        10,10,
        120,150,
        fenetrePrincipale,
        NULL,
        instance,
        NULL);
    {
        HWND hControle;
        // On le remplit avec un premier push button tout bête
        hControle=CreateWindow(
            "BUTTON",
            "Button 1",
            WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
            10+10,10+20,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_PUSHBUTTON_1,
            instance,
            NULL);
        // un second avec quelques flags : plat et plusieurs lignes
        hControle=CreateWindow(
            "BUTTON",
            "Button 2: texte long",
            WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON|BS_FLAT|BS_MULTILINE,
            10+10,10+50,
            100,40,
            fenetrePrincipale,
            (HMENU)ID_PUSHBUTTON_2,
            instance,
            NULL);
        // et un troisième un peu plus exotique, c'est aussi le bouton par défaut de la fenêtre
        // on lui met aussi une image alignée à gauche
        hControle=CreateWindow(
            "BUTTON",
            "",
            WS_CHILD|WS_VISIBLE|BS_DEFPUSHBUTTON|BS_ICON|BS_LEFT,
            10+10,10+100,
            100,40,
            fenetrePrincipale,
            (HMENU)ID_PUSHBUTTON_3,
            instance,
            NULL);

        HANDLE hIcon=LoadIcon(NULL,IDI_ERROR);
        SendMessage(hControle,BM_SETIMAGE,IMAGE_ICON,(LPARAM)hIcon);
    }
    // Deuxième groupe: les check box
    hGroup=CreateWindow(
        "BUTTON",
        "La check box",
        WS_CHILD|WS_VISIBLE|BS_GROUPBOX,
        130,10,
        120,150,
        fenetrePrincipale,
        NULL,
        instance,
        NULL);
    {
        HWND hControle;
        // une check box automatique deux états
        hControle=CreateWindow(
            "BUTTON",
            "Check box 1",
            WS_CHILD|WS_VISIBLE|BS_AUTOCHECKBOX,
            130+10,10+20,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_CHECKBOX_1,
            instance,
            NULL);
        // le même mais plat, et avec le texte à gauche de la case à cocher
        hControle=CreateWindow(
            "BUTTON",
            "Check box 2",
            WS_CHILD|WS_VISIBLE|BS_AUTOCHECKBOX|BS_FLAT|BS_LEFTTEXT,
            130+10,10+50,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_CHECKBOX_2,
            instance,
            NULL);

        // une autre check box simple automatique mais trois états
        hControle=CreateWindow(
            "BUTTON",
            "Check box 3",
            WS_CHILD|WS_VISIBLE|BS_AUTO3STATE,
            130+10,10+80,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_CHECKBOX_3,
            instance,
            NULL);

        // le même mais non automatique :)
        hControle=CreateWindow(
            "BUTTON",
            "Check box 4",
            WS_CHILD|WS_VISIBLE|BS_3STATE,
            130+10,10+110,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_CHECKBOX_4,
            instance,
            NULL);
    }
    // troisième groupe : les radio button
    hGroup=CreateWindow(
        "BUTTON",
        "Le radio button",
        WS_CHILD|WS_VISIBLE|BS_GROUPBOX,
        10,160,
        240,110,
        fenetrePrincipale,
        NULL,
        instance,
        NULL);
    {
        HWND hControle;
        // trois radio button
        hControle=CreateWindow(
            "BUTTON",
            "Choix 1.1",
            WS_CHILD|WS_VISIBLE|BS_AUTORADIOBUTTON|WS_GROUP,
            10+10,160+20,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_RADIOBUTTON_11,
            instance,
            NULL);

        hControle=CreateWindow(
            "BUTTON",
            "Choix 1.2",
            WS_CHILD|WS_VISIBLE|BS_AUTORADIOBUTTON,
            10+10,160+50,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_RADIOBUTTON_12,
            instance,
            NULL);

        hControle=CreateWindow(
            "BUTTON",
            "Choix 1.3",
            WS_CHILD|WS_VISIBLE|BS_AUTORADIOBUTTON,
            10+10,160+80,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_RADIOBUTTON_13,
            instance,
            NULL);
        // trois autres, mais qui forment un autre groupe
        hControle=CreateWindow(
            "BUTTON",
            "Choix 2.1",
            WS_CHILD|WS_VISIBLE|BS_AUTORADIOBUTTON|WS_GROUP,
            130+10,160+20,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_RADIOBUTTON_21,
            instance,
            NULL);

        hControle=CreateWindow(
            "BUTTON",
            "Choix 2.2",
            WS_CHILD|WS_VISIBLE|BS_AUTORADIOBUTTON,
            130+10,160+50,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_RADIOBUTTON_22,
            instance,
            NULL);

        hControle=CreateWindow(
            "BUTTON",
            "Choix 2.3",
            WS_CHILD|WS_VISIBLE|BS_AUTORADIOBUTTON,
            130+10,160+80,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_RADIOBUTTON_23,
            instance,
            NULL);
    }

}

VOID AfficheActionCheckBox(int index,HWND hCtl,HWND fenetrePrincipale)
{
    // test si la check box est cochée, non cochée ou inderterminée.
    LRESULT lRes=SendMessage(hCtl,BM_GETCHECK,0,0);
    CHAR lpMessage[128];

    sprintf(lpMessage,"La check box n°%d est maintenant %s",index,
        lRes==BST_CHECKED?"cochée":
        lRes==BST_UNCHECKED?"décochée":"inderterminée");

    MessageBox(fenetrePrincipale,lpMessage,"Vous avez modifié une check box",
        MB_ICONINFORMATION|MB_OK);
}

VOID DemandeActionCheckBox4(HWND hCtl,HWND fenetrePrincipale)
{
    // test si la check box est cochée, non cochée ou inderterminée.
    LRESULT lRes=SendMessage(hCtl,BM_GETCHECK,0,0);
    PCHAR lpProchainEtat;
    UINT iProchainEtat;
    UINT iChoix;
    CHAR lpMessage[128];

    if(lRes==BST_CHECKED)
    {
        lpProchainEtat="inderterminée";
        iProchainEtat=BST_INDETERMINATE;
    }
    else if(lRes==BST_INDETERMINATE)
    {
        lpProchainEtat="décochée";
        iProchainEtat=BST_UNCHECKED;
    }
    else
    {
        lpProchainEtat="cochée";
        iProchainEtat=BST_CHECKED;
    }

    sprintf(lpMessage,"Voulez-vous passer à l'état %s ?",lpProchainEtat);

    iChoix=MessageBox(fenetrePrincipale,lpMessage,"",MB_YESNO);
    if(iChoix==IDYES)
        SendMessage(hCtl,BM_SETCHECK,(WPARAM)iProchainEtat,0);
}

VOID NotificationControle(HWND fenetrePrincipale,UINT message, WPARAM wParam, LPARAM lParam)
{
    UINT iId=LOWORD(wParam);
    HWND hCtl=(HWND)lParam;

    switch(iId)
    {
        case ID_PUSHBUTTON_1:
            MessageBox(fenetrePrincipale,"Vous avez appuyé sur le premier bouton","",MB_OK);
            break;
        case ID_PUSHBUTTON_2:
            MessageBox(fenetrePrincipale,"Vous avez appuyé sur le second bouton","",MB_OK);
            break;
        case ID_PUSHBUTTON_3:
            MessageBox(fenetrePrincipale,"Vous avez appuyé sur le troisième bouton","",MB_OK);
            break;
        case ID_CHECKBOX_1:
        case ID_CHECKBOX_2:
        case ID_CHECKBOX_3:
            AfficheActionCheckBox(iId-ID_CHECKBOX_1,hCtl,fenetrePrincipale);
            break;
        case ID_CHECKBOX_4:
            DemandeActionCheckBox4(hCtl,fenetrePrincipale);
            break;
    }
}

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND boutons[2] = {NULL};

    switch (message)
    {
        case WM_CREATE:
            RemplieFenetrePrincipale(fenetrePrincipale);
            return 0;
        case WM_COMMAND:
            NotificationControle(fenetrePrincipale,message,wParam,lParam);
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
            return DefWindowProc(fenetrePrincipale,message,wParam,lParam);
    }
}

int WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
             LPSTR lignesDeCommande, int modeDAffichage)
{
    HWND fenetrePrincipale;
    MSG message;
    WNDCLASS classeFenetre;

    instance = cetteInstance;

    classeFenetre.style = 0;
    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
    classeFenetre.cbClsExtra = 0;
    classeFenetre.cbWndExtra = 0;
    classeFenetre.hInstance = NULL;
    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    classeFenetre.lpszMenuName = NULL;
    classeFenetre.lpszClassName = "classeF";

// On prévoit quand même le cas où ça échoue
    if(!RegisterClass(&classeFenetre))
        return FALSE;

    fenetrePrincipale=CreateWindow(
        "classeF",
        "Ma premiere fenetre winAPI !",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,CW_USEDEFAULT,
        270,320,
        NULL,
        NULL,
        cetteInstance,
        NULL);

    if (!fenetrePrincipale)
        return FALSE;

    ShowWindow(fenetrePrincipale,modeDAffichage);
    UpdateWindow(fenetrePrincipale);

    while(GetMessage(&message,NULL,0,0))
    {
        TranslateMessage(&message);
        DispatchMessage(&message);
    }

    return message.wParam;
}

Rédacteur : pamaury

Le contrôle d'édition

Définition

Le contrôle d'édition est un contrôle rectangulaire utilisé dans les boîtes de dialogue pour permettre à l'utilisateur d'entrer et d'éditer du texte au clavier. On peut également l'utiliser dans une fenêtre standard.

Création

Pour créer un Contrôle d'édition, il faut appeler CreateWindow en spécifiant "EDIT" comme classe. Dans un fichier ressource, on écrira par exemple :

CONTROL "", IDC_NOMDUCONTROLE, <couleur nom="bleu">"EDIT"</couleur>, WS_CHILD | WS_VISIBLE, x, y, largeur, hauteur

Vous pouvez aussi utiliser le mot clé EDITTEXT.

Néanmoins, cela ne suffit pas car comme vous vous en doutez, nous avons plusieurs possibilités pour créer notre contrôle. Ce qui va nous intéresser est donc l'attribut de style . En plus de spécifier les styles habituels (WM_CHILD,WM_VISIBLE), vous pouvez définir un ou plusieurs de ces styles spécifiques qui commencent tous par "ES_" :

  • ES_AUTOHSCROLL : défilement automatique du texte de 10 caractères vers la droite quand l'utilisateur tape un caractère à la fin de la ligne. Quand l'utilisateur presse sur la touche "Entrée", le contrôle fait revenir tout le texte à la position d'origine.

  • ES_AUTOVSCROLL : fait défiler le texte automatiquement d'une page vers le haut quand l'utilisateur presse sur la touche "Entrée" à la dernière ligne.

  • ES_CENTER : centre le texte.

  • ES_LEFT : aligne le texte à gauche.

  • ES_LOWERCASE : convertit en minuscules tous les caractères tapés dans le contrôle.

  • ES_MULTILINE : par défaut, un contrôle edit n'est constitué que d'une seule ligne. Ce style affiche un contrôle multiligne.

  • ES_NOHIDESEL : quand le contrôle perd le focus ; par défaut, la sélection de texte est cachée, et lorsqu'il a le focus, l'affichage du texte sélectionné est inversé. Avec ce style, le texte sélectionné est inversé, que le contrôle ait le focus ou non.

  • ES_NUMBER : ne permet que les chiffres.

  • ES_OEMCONVERT : convertit le texte entré dans le contrôle, de sorte que le texte tapé est mappé du code ANSI vers le code OEM, et inversement.

  • ES_PASSWORD : affiche un astérisque * pour chaque caractère entré dans le contrôle Edit. Ce style n'est valide que pour les contrôles d'une seule ligne.

  • ES_READONLY : empêche l'utilisateur de taper ou d'éditer du texte dans la commande d'édition.

  • ES_RIGHT : aligne le texte à droite.

  • ES_UPPERCASE : convertit en majuscules tous les caractères tapés dans le contrôle.

  • ES_WANTRETURN : indique qu'un retour de chariot est inséré quand l'utilisateur presse sur la touche "Entrée", si le texte est écrit dans un contrôle Edit multiligne dans une boîte de dialogue. Si ce style n'est pas spécifié, presser sur le touche "Entrée" aura le même effet que de presser sur le bouton par défaut de la boîte de dialogue. Ce style n'a pas d'effet sur un contrôle Edit d'une ligne.

Il est évident que vous ne devez pas combiner des styles contradictoires, comme le texte affiché à droite et à gauche, ou le style texte en minuscules et texte en majuscules...

Nous avons créé le contrôle Edit, nous allons maintenant étudier les façons de l'utiliser. Ainsi, dans un premier temps, nous traiterons des notifications envoyées par le contrôle à la fenêtre parent, puis nous verrons les messages envoyés par la fenêtre parent au contrôle.

Les notifications

L'utilisateur envoie des requêtes d'édition en utilisant le clavier ou la souris. Le système envoie chaque requête à la fenêtre parent du contrôle dans le message WM_COMMAND.
J'ai considéré que vous savez comment interagir avec ce message, et je décrirai donc ici uniquement les codes de notification.

EN_ALIGN_LTR_EC

Cette notification est envoyée quand l'utilisateur change de direction dans le contrôle d'édition de gauche vers la droite.

EN_ALIGN_RTL_EC

Cette notification est envoyée quand l'utilisateur change de direction dans le contrôle d'édition de droite vers la gauche.

EN_CHANGE

L'utilisateur a modifié le texte du contrôle. Le message est envoyé après que le texte soit affiché (différent de EN-UPDATE).

Si l'on traite cette notification on dira : "s'il y a un changement dans le contrôle, faire ceci..."

EN_HSCROLL

L'utilisateur a cliqué sur la barre horizontale de défilement du contrôle d'édition.

Le système envoie ce message avant de mettre à jour l'écran.

EN_MAXTEXT

Quand l'utilisateur insère du texte, et que le nombre de caractères entrés est supérieur à ce qui est possible pour ce contrôle, l'insertion est tronquée.

Ce message est envoyé quand le contrôle d'édition n'a pas le style ES_AUTOHSCROLL, et que le nombre de caractères insérés excède la largeur du contrôle ; ou quand le contrôle d'édition n'a pas le style ES_AUTOHSCROLL, et que le nombre de lignes de texte entrées dépassent la hauteur du contrôle.

EN_UPDATE

L'utilisateur a modifié le texte dans le contrôle d'édition, et le système va afficher le nouveau texte.

Le système envoie cette notification après avoir formaté le texte, mais avant de l'afficher. Ainsi, l'application peut redimensionner le contrôle.

EN_VSCROLL

L'utilisateur a cliqué sur la barre verticale de défilement, ou a utilisé la roulette de la souris au-dessus du contrôle d'édition.

Le système envoie le message avant la remise à jour de la page.

WM_CTLCOLOREDIT

Ce message est envoyé par le système à la fenêtre parent avant que le contrôle ne soit dessiné. Ce message contient le handle du contexte d'affichage (DC) dans wparam, et le handle de la fenêtre fille dans lparam. La fenêtre parent peut utiliser ces handle pour changer les couleurs du texte et du fond du contrôle d'édition.

- Ce message est envoyé directement à la fonction de gestion des messages, et non pas via le message WM_COMMAND.
- La brosse retournée ne sera pas automatiquement détruite par le système.

Les messages

Ces messages peuvent être envoyés au contrôle avec la fonction :

SendMessage(hCtl,iCode,wParam,lParam)

Pour chaque message, je décrirai le rôle et la signification de wParam et lParam et de la valeur retournée.

EM_GETCUEBANNER

Recherche une sélection textuelle dans le texte affiché.

  • wParam : pointeur sur une chaîne de caractères unicode qui contient la sélection textuelle.

  • lParam : taille du buffer qui contient cette sélection.

  • Valeur retournée : TRUE si la sélection a été trouvée, FALSE en cas d'échec.

EM_GETFIRSTVISIBLELINE

Ce message recherche l'index de la ligne visible la plus haute dans un contrôle d'édition multiligne.

  • wParam : 0.

  • lParam : 0.

  • Valeur retournée : index de la ligne.

    Pour un contrôle d'édition d'une seule ligne, cet index sera celui du premier caractère visible de la ligne.

EM_GETHANDLE

Ce message renvoie le handle de la mémoire courante allouée au texte d'un contrôle d'édition multiligne.

  • wParam: 0.

  • lParam : 0.

  • Valeur retournée : handle du buffer qui contient le texte du contrôle d'édition. Si une erreur survient, comme par exemple l'envoi du texte à un contrôle d'une seule ligne, cette valeur sera 0.

Si la fonction SendMessage réussit, l'application peut accéder au contenu du contrôle d'édition, en donnant la structure HLOCAL à la valeur retournée avant de la passer à la fonction LocalLock. Pour déverrouiller le buffer, et ainsi pouvoir modifier son contenu, l'application doit au préalable utiliser la fonction LocalUnlock.

EM_GETLIMITTEXT

Recherche les limites du texte courant.

  • wParam : 0.

  • lParam : 0.

  • Valeur retournée : limite du texte.

La limite du texte est la quantité maximum de texte, en TCHAR, que le contrôle peut contenir. Pour un texte en ANSI, c'est le nombre de bytes ; pour un texte en Unicode, ce sera le nombre de caractères. Deux documents avec la même limite de caractères rapporteront la même limite de texte, même si l'un est en norme ANSI, et l'autre en Unicode.

EM_GETLINE

Ce message copie une ligne de texte depuis un contrôle d'édition, et le place dans un buffer défini.

  • wParam : indique l'index de la ligne à rechercher dans un contrôle d'édition multiligne. Si ce paramètre est 0, la ligne recherchée sera la ligne la plus haute dans le texte. Si le contrôle ne contient qu'une seule ligne, ce paramètre sera ignoré.

  • lParam : pointeur sur le buffer qui va recevoir la copie de la ligne. Avant d'envoyer le message, définissez le premier mot de ce buffer à sa taille en TCHAR. Pour un texte ANSI, c'est le nombre de bytes ; pour un texte Unicode c'est le nombre de caractères. Cette taille dans le premier mot sera remplacée par la copie de la première ligne.

  • Valeur retournée : nombre de TCHAR copiés. La valeur retournée sera 0 si le nombre de lignes spécifié dans le paramètre wParam est plus grand que le nombre de lignes contenu dans le contrôle d'édition.

La copie de la ligne ne contient pas le caractère nul final \0.

EM_GETLINECOUNT

Ce message recherche le nombre de lignes dans un contrôle d'édition multiligne.

  • wParam : 0.

  • lParam : 0.

  • Valeur retournée : integer, qui définit le nombre total de lignes de texte contenu dans un contrôle d'édition multiligne. Si le contrôle ne contient pas de texte, la valeur retournée sera 1. Cette valeur ne sera jamais inférieure à 1.

Ce message recherche le nombre total de lignes de texte, et pas seulement le nombre de lignes de texte visible. Si le dispositif de retour à la ligne automatique est désactivé, le nombre de lignes peut changer quand les dimensions de la fenêtre d'édition changent.

EM_GETMODIFY

Ce message recherche l'état du flag de modification du contrôle d'édition. Le flag indique si le contenu du contrôle a été modifié.

  • wParam : 0.

  • lParam : 0.

  • Valeur retournée : non nulle s'il y a une modification, 0 s'il n'y a pas de modification.

On peut envoyer le message EM_SETMODIFY au contrôle d'édition pour appliquer ou enlever le flag.

EM_GETPASSWORDCHAR

Ce message recherche le caractère d'un mot de passe que l'utilisateur entre dans le contrôle d'édition.

  • wParam : 0.

  • lParam : 0.

  • Valeur retournée : définit le caractère qui doit être affiché à la place des caractères entrés par l'utilisateur. Si cette valeur est NULL, il n'y a pas de caractères de mot de passe, et le contrôle affiche les caractères entrés par l'utilisateur.

Si un contrôle d'édition est créé avec le style ES_PASSWORD, le caractère par défaut du mot de passe sera un astérisque (*). Si un contrôle d'édition est créé sans le style ES_PASSWORD, il n'y aura pas de caractères de mot de passe ; pour changer les caractères du mot de passe, envoyez le message EM_SETPASSWORDCHAR.

EM_GETRECT

Ce message recherche le rectangle de formatage d'un contrôle d'édition. Le rectangle de formatage est le rectangle dans lequel le contrôle affiche le texte. Ce rectangle est indépendant de la taille de la fenêtre du contrôle d'édition.

  • wParam : 0.

  • lParam : pointeur sur une structure RECT qui reçoit le rectangle de formatage.

  • Valeur retournée : cette valeur n'a pas de grande signification.

Vous pouvez modifier le rectangle de formatage d'un contrôle d'édition multiligne en utilisant les messages EM_SETRECT et EM_SETRECTNP.
Sous certaines conditions, EM-GETRECT ne va pas retourner exactement les valeurs définies par EM_SETRECT ou EM_SETRECTNP, mais elles seront approximativement correctes avec une variation de quelques pixels.

EM_GETSEL

Message qui recherche les positions des caractères de début et de fin d'une sélection dans le contrôle d'édition.

  • wParam : pointeur sur un buffer qui reçoit la position de départ de la sélection. Ce paramètre peut être NULL.

  • lParam : pointeur sur un buffer qui reçoit la position de premier caractère non sélectionné après la fin de la sélection. Ce paramètre peut être NULL.

  • Valeur retournée : valeur contenant la position de départ de la sélection dans le mot bas, et la position du premier caractère après le dernier sélectionné dans le mot haut. Si l'une de ces valeurs excède 65,535, la valeur retournée sera 1.

EM_GETWORDBREAKPROC

Ce message recherche l'adresse de la fonction courante de retour à la ligne.

  • wParam : 0.

  • lParam : 0.

  • Valeur retournée : définit l'adresse de la fonction Wordwrap (retour à la ligne) de l'application. Cette valeur est NULL si aucune fonction Wordwrap n'existe.

Une fonction Wordwrap balaie le texte du buffer contenant le texte envoyé pour être affiché. Elle cherche le premier mot qui n'apparaît pas sur la ligne d'affichage. La fonction Wordwrap place ce mot au début de la ligne suivante. Une fonction Wordwrap définit le point où le système doit casser la ligne du texte dans un contrôle d'édition multiligne, normalement au niveau d'un caractère d'espacement entre deux mots.

EM_HIDEBALLOONTIP

Cache toute info bulle associée au contrôle d'édition.

  • wParam : 0.

  • lParam : 0.

  • Valeur retournée : si le message réussit, cette valeur sera TRUE, sinon, elle sera FALSE.

EM_LINEFROMCHAR

Ce message recherche l'index de la ligne contenant l'index d'un caractère défini, dans un contrôle d'édition. L'index d'un caractère appartient à une suite dont le premier terme est 0.

  • wParam : définit l'index du caractère contenu dans la ligne dont le numéro est recherché. Si ce paramètre est 1, EM_LINEFROMCHAR recherche soit le numéro de la ligne courante (la ligne où se trouve le curseur), soit, si une sélection existe, le numéro de la ligne où se situe le début de la sélection de texte.

  • lparam : 0.

  • Valeur retournée : numéro de la ligne contenant l'index du caractère spécifié dans wParam. Ce numéro appartient à une série qui commence à 0.

EM_LINEINDEX

Ce message définit la limite de texte d'un contrôle d'édition. Cette limite est la quantité maximum de texte, en TCHAR, que l'utilisateur peut entrer dans le contrôle d'édition.

  • wParam : spécifie le nombre maximum de TCHAR que l'utilisateur peut entrer. Pour le texte ANSI, c'est le nombre de bytes ; pour le texte Unicode, c'est le nombre de caractères. Ce nombre ne doit pas inclure le caractère nul terminal.

  • lParam : 0

  • Valeur retournée : pas de valeur retournée.

Ce message limite seulement le texte que l'utilisateur peut entrer. Il ne concerne pas le texte qui est déjà dans le contrôle quand le message est envoyé, ni la longueur du texte copié dans le contrôle d'édition par le message WM_SETTEXT. Si une application utilise le message WM_SETTEXT pour placer plus de texte que le message EM_LIMITTEXT a établi dans un contrôle d'édition, l'utilisateur pourra éditer entièrement le contenu du contrôle d'édition.

Avant que EM_LIMITTEXT soit appelé, la limite par défaut de la quantité de texte que l'utilisateur peut entrer dans un contrôle d'édition est de 32,767 caractères.

EM_LINELENGTH

Ce message recherche la longueur, en caractères, d'une ligne dans un contrôle d'édition.

  • wParam : définit l'index du caractère dans une ligne dont la longueur est recherchée. Si ce paramètre est plus grand que le nombre de caractères dans le contrôle, la valeur retournée sera 0. Ce paramètre peut être 1. Dans ce cas, le message retourne le nombre de caractères non sélectionnés dans la ligne des caractères sélectionnés. Par exemple, si une sélection va du 4ème caractère d'une ligne au 8ème caractère avant la fin de la ligne qui suit, la valeur retournée sera 10 (3 caractères sur la première ligne, et 7 sur la ligne suivante).

  • lParam : 0.

  • Valeur retournée : pour des contrôles d'édition multiligne, la valeur retournée est la longueur, en TCHAR, de la ligne définie par le paramètre wParam. Pour le texte ANSI, c'est le nombre de bytes ; pour le texte Unicode, c'est le nombre de caractères. Cette valeur n'inclut pas le caractère de retour de chariot de fin de ligne.
    Pour un contrôle d'une seule ligne, la valeur retournée est la longueur, en TCHAR, du texte dans le contrôle d'édition.

Utilisez le message EL_LINEINDEX pour rechercher l'index d'un caractère pour un numéro de ligne donné à l'intérieur d'un contrôle d'édition multiligne.

EM_LINESCROLL

Fait défiler le texte dans un contrôle d'édition multiligne.

  • wParam : définit le nombre de caractères à faire défiler horizontalement.

  • lParam : définit le nombre de lignes à faire défiler verticalement.

  • Valeur retournée : si le message est envoyé à un contrôle d'édition multiligne, la valeur est TRUE. Si le message est envoyé à un contrôle d'édition d'une ligne la valeur est FALSE.

EM_POSFROMCHAR

Ce message recherche les coordonnées dans la zone client d'un caractère dans le contrôle d'édition.

  • wParam : index du caractère (les index commencent par 0).

  • lParam : 0.

  • Valeur retournée : contient les coordonnées du caractère dans la zone client. Le mot bas contient la coordonnée horizontale, et le mot haut contient la coordonnée verticale.

Une coordonnée retournée peut être négative si le caractère défini n'est pas affiché dans l'aire client du contrôle d'édition. Si le caractère est un délimiteur de ligne, les coordonnées retournées seront juste après le dernier caractère visible de la ligne. Si l'index spécifié est plus grand que l'index du dernier caractère dans le contrôle, le contrôle retournera -1.

EM_REPLACESEL

Ce message remplace la sélection courante dans le contrôle d'édition avec le texte spécifié.

  • wParam : spécifié seulement si l'opération peut être annulée. Si le paramètre est TRUE, l'opération peut être annulée. Si le paramètre est FALSE, l'opération ne peut pas être annulée.

  • lParam : pointeur sur une chaîne à terminaison nulle contenant le texte de remplacement.

  • Valeur retournée : ne retourne pas de valeur.

Pour remplacer tout le texte d'un contrôle d'édition, utilisez le message WM_SETTEXT.
S'il n'y a pas de sélection courante de texte, le texte de remplacement est inséré à l'endroit où est placé le curseur.

EM_SCROLL

Ce message fait défiler le texte verticalement dans un contrôle d'édition. Ce message est équivalent au message WM_VSCROLL.

  • wParam : peut être une des valeurs suivantes :
    *** SB_LINEDOWN : déplace les lignes vers le bas
    *** SB_LINEUP : déplace les lignes vers le haut.
    *** SB_PAGEDOWN : passe à la page suivante.
    *** SB_PAGEUP : passe à la page précédente.

  • lParam : 0.

  • Valeur retournée : si le message réussit, le mot haut retourne la valeur TRUE, et le mot bas est le nombre de lignes de défilement. Si le wParam spécifie une valeur invalide, cette valeur sera FALSE.

EM_SCROLLCARET

Ce message fait avancer le curseur dans le contrôle d'édition.

  • wParam : ce paramètre est réservé. Il peut être défini à 0.

  • lParam : ce paramètre est réservé. Il peut être défini à 0.

  • Valeur retournée : sans grande signification.

EM_SETCUEBANNER

Le texte du contrôle est affiché comme information, et va s'en aller dès que l'utilisateur commencera à utiliser le contrôle.

  • wParam : 0

  • lParam : pointeur sur une chaîne de caractères Unicode, qui contient le texte à afficher en "information".

  • Valeur retournée : si le message réussit, cette valeur sera TRUE ; dans le cas contraire, FALSE.

EM_SETHANDLE

Message qui définit le handle de la mémoire qui va être utilisée par un contrôle d'édition.

  • wParam : handle de la mémoire du buffer que le contrôle d'édition utilise pour placer le texte courant affiché, au lieu d'utiliser sa propre mémoire.

  • lParam : 0

  • Valeur retournée : pas de valeur retournée.

Avant qu'une application ne définisse un nouveau handle de mémoire, elle doit envoyer un message EM_GETHANDLE pour rechercher le handle de la mémoire courante du buffer, et doit libérer cette mémoire.

EM_SETLIMITTEXT

Ce message définit la limite du texte d'un contrôle d'édition. La limite du texte est la quantité maximum de texte, en TCHAR, que l'utilisateur peut entrer dans le contrôle d'édition.
C'est le même message que EM_LIMITTEXT.

  • wParam : définit le nombre maximum de TCHAR que l'utilisateur peut entrer. Pour un texte ANSI, c'est le nombre de bytes ; pour un texte Unicode, c'est le nombre de caractères. Ce nombre ne doit pas inclure le caractère nul de fin.

  • lParam : 0.

  • Valeur retournée : pas de valeur retournée.

Ce message limite seulement le texte que l'utilisateur peut entrer. Il n'affecte pas le texte qui est déjà dans le contrôle d'édition quand le message est envoyé. Il n'affecte pas non plus la longueur du texte copié dans le contrôle d'édition à l'aide du message WM_SETTEXT. Si une application utilise le message WM_SETTEXT pour placer plus de texte que ne le définit EM_SETLIMITTEXT à l'intérieur du contrôle, l'utilisateur pourra éditer la totalité du contenu du contrôle d'édition.
Avant que EM_SETLIMITTEXT soit appelé, la limite par défaut de la quantité de texte qu'un utilisateur peut entrer dans un contrôle d'édition est 32,767 caractères.

EM_SETMODIFY

Ce message applique ou annule le flag de modification pour un contrôle d'édition. Le flag de modification indique si le texte à l'intérieur d'un contrôle d'édition a été modifié.

  • wParam : donne la nouvelle valeur pour le flag de modification . Une valeur TRUE indique que le texte a été modifié, une valeur FALSE indique qu'il n'y a pas eu de modification.

  • lParam : 0

  • Valeur retournée : pas de valeur retournée.

Le système met immédiatement le flag de modification à zéro quand le contrôle est créé. Si un utilisateur change le texte du contrôle, le système définit à "nonzéro" le flag. Vous pouvez envoyer le message EM_GETMODIFY pour trouver le stade du flag de modification du contrôle d'édition.

EM_SETPASSWORDCHAR

Ce message définit ou annule le caractère de mot de passe pour un contrôle d'édition. Quand le caractère de mot de passe est défini, ce caractère est affiché à la place des caractères entrés par l'utilisateur.

  • wParam : définit le caractère à afficher à la place des caractères entrés par l'utilisateur. Si ce paramètre est zéro, le contrôle annule le caractère courant de mot de passe, et affiche les caractères entrés au clavier par l'utilisateur.

  • lParam : 0.

  • Valeur retournée : pas de valeur retournée.

    • Quand un contrôle d' édition reçoit le message EM_SETPASSWORDCHAR, le contrôle redessine tous les caractères visibles en utilisant le caractère spécifié dans wParam. Si wParam est zéro, le contrôle redessine tous les caractères visibles en utilisant les caractères qui ont été entrés par l'utilisateur.
    Si un contrôle d'édition a été créé avec le style ES_PASSWORD, le caractère de mot de passe par défaut est l'astérisque(*).

EM_SETREADONLY

Ce message définit ou annule le style ES_READONLY d'un contrôle d'édition.

  • wParam : TRUE si le style ES_READONLY est défini, FALSE sinon.

  • lParam : 0.

  • Valeur retournée : "non zéro" si la fonction réussit. Zéro si la fonction échoue.

EM_SETRECT

Ce message définit le rectangle de formatage d'un contrôle d'édition multiligne. Le rectangle de formatage est le rectangle dans lequel le contrôle dessine le texte. Il est indépendant de la taille de la fenêtre du contrôle d'édition.

  • wParam : 0.

  • lParam : pointeur sur une structure RECT qui définit les nouvelles dimensions du rectangle. Si le paramètre est NULL, le rectangle de formatage est défini aux valeurs par défaut.

  • Valeur retournée : pas de valeur retournée.

Si le contrôle d'édition n'a pas de barre horizontale de défilement, et si le rectangle de formatage est plus large que la fenêtre du contrôle d'édition, les lignes de textes qui seront plus larges que la fenêtre du contrôle seront coupées au lieu d'être terminées sur la ligne suivante.

Si un contrôle d'édition comporte un bord, le rectangle de formatage sera réduit par la taille du bord. Si vous voulez ajuster le rectangle renvoyé par EM_GETRECT, vous devez annuler la taille du bord avant d'utiliser le message EM_SETRECT.

EM_SETRECTNP

Ce message définit le rectangle de formatage d'un contrôle d'édition. Ce message est le même que le message EM_SETRECT à ceci près qu'il ne redessine pas la fenêtre du contrôle d'édition.

  • wParam : 0.

  • lParam : pointeur sur une structure RECT qui définit les nouvelles dimensions du rectangle. Si le paramètre est NULL, le rectangle de formatage est défini aux valeurs par défaut.

  • Valeur retournée : pas de valeur retournée.

EM_SETSEL

Ce message sélectionne une suite de caractères dans un contrôle d'édition.

  • wParam : spécifie la position du caractère situé au début de la sélection.

  • lParam : spécifie la position du caractère de fin de la sélection.

  • Valeur retournée : pas de valeur retournée.

La valeur de départ peut être plus grande que la la valeur de fin. La plus petite valeur définira la position du premier caractère dans la sélection. Inversement, la plus grande valeur définira la position du caractère de fin de la sélection.

Si le départ est 0 et la fin 1, tout le texte dans le contrôle d'édition sera sélectionné.
Si le départ est 1, la sélection sera annulée.

EM_SHOWBALLOONTIP

Ce message affiche une info-bulle associée à un contrôle d'édition.

  • wParam : 0.

  • lParam : pointeur sur une structure EDITBALLOONTIP contenant l'information concernant l'info-bulle à afficher.

  • Valeur retournée : si le message réussit, la valeur retournée sera TRUE ; sinon, FALSE.

EM_UNDO

Ce message annule la dernière opération du contrôle d'édition dans la file des annulations du contrôle.

  • wParam : 0.

  • lParam : 0.

  • Valeur retournée : pour un contrôle d'une seule ligne, cette valeur est toujours TRUE. Pour un contrôle multiligne, cette valeur est TRUE si l'annulation a réussi, ou FALSE dans le cas contraire.

Une annulation peut également être annulée. Par exemple, vous pouvez restaurer du texte effacé avec le message EM_UNDO, et annuler une nouvelle fois le texte avec ce message aussi longtemps qu'aucune autre opération ne se placera dans la file.

Exemple

#include <windows.h>
#include <stdio.h>

#define ID_BOUTON1      110
#define ID_BOUTON2      111
#define ID_BOUTON3      112

//variables globales
HINSTANCE inst;
HWND hEdit[2]; //handle des deux contrôles d'édition

//Afficher un contrôle d'édition d'une ligne
HWND afficheEditUneLigne (HWND hwnd)
{
HWND hEdit=CreateWindow(
                        "EDIT",
                        "Modifiez le texte SVP",
                         WS_VISIBLE|WS_CHILD|WS_BORDER|ES_CENTER ,
                         50,10,500,20,hwnd,NULL,inst,NULL);

return hEdit;
}

//Afficher un contrôle d'édition multiligne
HWND afficheEditMultiligne (HWND hwnd)
{
    char texte[2000]={0};
strncat (texte, "Vous voudriez apprendre l'API windows et vous trouvez les autres ",1000);
strncat (texte, "cours présents sur le net trop difficiles, ou pas assez précis ?",1000);
strncat (texte, "Vous avez frappé à la bonne porte. Bienvenue dans ce tutorial.",1000);
strncat (texte, "Vous allez apprendre ici à vous servir d'une API très développée :",1000);
strncat (texte, "l'API Win32. Sa proximité avec le système d'exploitation lui permet ",1000);
strncat (texte, "d'avoir un contrôle total sur n'importe quelle partie de l'OS et un ",1000);
strncat (texte, "grand éventail de fonctions. L'API Windows est, malheureusement et comme ",1000);
strncat (texte, "son nom l'indique, exclusivement dédié à Windows. Donc si vous ",1000);
strncat (texte, "possédez un autre OS, passez votre chemin. Je considère dans ce tutorial que ",1000);
strncat (texte, "vous ne connaissez strictement rien à l'API Windows, ceci pour ne pas ",1000);
strncat (texte, "décourager les débutants, néammoins, vous aurez besoin des connaissances ",1000);
strncat (texte, "acquises tout au long du tuto de M@teo. Vous trouverez donc ici des ",1000);
strncat (texte, "cours évolutifs, une documentation sur les différents messages, ",1000);
strncat (texte, "notifications, styles, fonctions etc., des projets grands, ",1000);
strncat (texte, "moyens ou petits ainsi que des bibliothèques statiques  ",1000);
strncat (texte, "ou dynamiques, et enfin des annexes. ",1000);

HWND hEdit=CreateWindow("EDIT",
                        texte,
                        WS_VISIBLE|WS_CHILD|ES_MULTILINE|ES_AUTOVSCROLL,
                        50,50,500,300,hwnd,NULL,inst,NULL);

return hEdit;
}

//Traitement des messages
void messageCtl(HWND hwnd,UINT message, WPARAM wParam, LPARAM lParam)
{
UINT motBas =  LOWORD(wParam);
HWND hCtl= (HWND) lParam;
int nombreLignes=0;
char lignes[70]={0},nLignes[5]={0};

        switch(motBas)
            {
            case ID_BOUTON1://nombre de lignes

            //envoyer le message approprié pour trouver le nombre de lignes dans le contôle
               nombreLignes = SendMessage( hEdit[1], EM_GETLINECOUNT,wParam,lParam);

            //transformer un integer en chaîne de caractères et afficher le résultat
                sprintf(nLignes,"%d",nombreLignes);
                strncat(lignes,"Il y a ",70);
                strncat(lignes,nLignes,70);
                strncat(lignes, " lignes dans ce texte. Supprimez ou ajoutez \n en et testez le message.",70);
                MessageBox(hwnd,lignes,"",MB_OK);
            break;
            case ID_BOUTON2: //marges de gauche
                wParam = EC_LEFTMARGIN ;
                LOWORD(lParam) = 100;
                //ou
                //lParam = (LPARAM)MAKELONG (100,0);
                //envoi du message au contrôle d'édition
                SendMessage( hEdit[1], EM_SETMARGINS,wParam,lParam);
                MessageBox(hwnd,"A vous de trouver comment réduire \nles lignes maintenant ! :)","",MB_OK);
            break;
            }
}

//Traitement des notifications
void NotificationControle(HWND hwnd,UINT message, WPARAM wParam, LPARAM lParam)
{
UINT motHaut= HIWORD(wParam);
HWND hCtl= (HWND) lParam;

switch(motHaut)
{
        //Notification EN_CHANGE envoyée par le contrôle
        case EN_CHANGE:
                if (hCtl == hEdit[0])
                {
                MessageBox(hwnd,"Vous avez modifié le texte d'une contrôle à une ligne !!","",MB_OK);
                }
        break;
        case BN_CLICKED: // cliquer sur un des boutons pour analyser les messages
               messageCtl((HWND)hwnd,(UINT)message,(WPARAM)wParam,(LPARAM)lParam);
        break;
}
}

//Boutons pour la démo
HWND boutonMessage(HWND hwnd,HMENU code,long x, long y, char * texte)
{
HWND hBouton=CreateWindow(
                        "BUTTON",
                        texte,
                        WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
                        x,y,
                        350,30,
                        hwnd,
                        code,
                        inst,
                        NULL);
return hBouton;
}

LRESULT CALLBACK  Procedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{

    static HWND hBouton[3]={0};

switch (message)

    {
        //afficher les contrôles d'édition à la réception du message WM_CREATE
        case WM_CREATE:
               hEdit[0]=afficheEditUneLigne (hwnd);
               hEdit[1]=afficheEditMultiligne(hwnd);
        //affichage des boutons pour la démo:
               hBouton[0]= boutonMessage(hwnd, (HMENU)ID_BOUTON1,120,355,"Tester le message EM_GETLINECOUNT");
               hBouton[1]= boutonMessage(hwnd, (HMENU)ID_BOUTON2,120,395,"Test le EM_SETMARGINS (Gauche)");
               hBouton[2]= boutonMessage(hwnd, (HMENU)ID_BOUTON3,120,435,"Test le EM_SETMARGINS (Droite)");
        return 0;

        case WM_COMMAND:
            //Recevoir une notification
            NotificationControle(hwnd,message,wParam,lParam);
              return 0;

        case WM_CLOSE:
             DestroyWindow(hwnd);
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, message, wParam, lParam);
        }
}

int WINAPI WinMain(HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
                                        LPSTR lignesDeCommande, int modeDAffichage)
{
       //Variables de la fonction principale
    MSG msg;
    WNDCLASS wc;
        HWND hwnd;
        inst = cetteInstance;
        // Structure de la classe de la fenêtre principale
    wc.style = 0 ;
    wc.lpfnWndProc = Procedure;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = cetteInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground =  (HBRUSH)(1+ COLOR_BTNFACE);
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "ClassePrincipale";

        //Enregistrer la classe de fenêtre
    if(!RegisterClass(&wc)) return FALSE;
    hwnd = CreateWindow("ClassePrincipale", "Démo du contrôle d'édition",WS_OVERLAPPEDWINDOW   ,
                              200,100,600 ,500, NULL, NULL, cetteInstance, NULL);
        if (!hwnd)  return FALSE;
    ShowWindow(hwnd,SW_SHOW);
        UpdateWindow( hwnd );

        //Boucle de message
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

Rédactrice : acryline

La liste déroulante

Ce contrôle est appelé sous Windows une COMBOBOX, et permet d'afficher une liste déroulante de choix.

Création

Pour créer une combobox, il faut appeler CreateWindow en spécifiant "COMBOBOX" comme classe.

En ressource, utilisez le mot-clé CONTROL (dont l'utilisation est spécifiée plus loin), ou encore (plus adapté) COMBOBOX sous la forme suivante :

COMBOBOX ID_COMBO, x, y, w, h, STYLES

Néanmoins, cela ne suffit pas, car comme vous l'avez vu, il y a plusieurs possibilités. Ce qui va nous intéresser est donc l'attribut de style. En plus de spécifier les styles habituels (WM_CHILD,WM_VISIBLE), vous pouvez spécifier un ou plusieurs de ces styles :

  • CBS_AUTOHSCROLL : défile automatiquement le texte vers la droite dans le contrôle EDIT quand l'utilisateur tape un caractère à la fin de la ligne. Si ce style n'est pas défini, l'utilisateur ne peut saisir plus de caractères que ceux qui tiennent dans le contrôle EDIT.

  • CBS_DISABLENOSCROLL : dessine une barre de défilement vertical désactivée quand il n'y a pas assez d'éléments. Sans ce style, la barre verticale n'est pas affichée.

  • CBS_DROPDOWN : similaire à CBS_SIMPLE, sauf que la liste n'est pas affichée tant que l'utilisateur ne clique pas sur le contrôle EDIT.

  • CBS_DROPDOWNLIST : similaire à CBS_DROPDOWN, sauf que le contrôle EDIT ne peut pas être modifié, et affiche le texte de l'élément sélectionné (ou rien si aucun élément n'est sélectionné).

  • CBS_HASSTRINGS : spécifie une COMBOBOX dessinée par l'utilisateur, contenant des chaînes de caractères.

  • CBS_LOWERCASE : convertit en minuscules tous les textes.

  • CBS_NOINTEGRALHEIGHT : spécifie que la taille de la COMBOBOX soit exactement celle spécifiée par l'application, au lieu d'arrondir pour n'afficher que des éléments entiers.

  • CBS_OEMCONVERT : convertit le texte entré dans le EDIT vers le set de caractères OEM, puis reconvertit vers le set de caractères Windows.

  • CBS_OWNERDRAWFIXED : spécifie une COMBOBOX dessinée par l'utilisateur : tous les éléments ont la même hauteur

  • CBS_OWNERDRAWVARIABLE : spécifie une COMBOBOX dessinée par l'utilisateur : tous les éléments n'ont pas la même hauteur.

  • CBS_SIMPLE : affiche une liste en permanence, et pas seulement quand l'utilisateur clique dessus. La sélection est affichée dans le EDIT.

  • CBS_SORT : trie automatiquement les chaînes de caractères.

  • CBS_UPPERCASE : convertit automatiquement en majuscules les caractères.

Notifications

CBN_CLOSEUP

Cette notification est envoyée lorsque la liste déroulante a été fermée.

CBN_DBLCLK

Cette notification est envoyée lorsque l'utilisateur double-clique sur un élément de la liste.

CBN_DROPDOWN

Cette notification est envoyée lorsque la liste déroulante a été ouverte.

CBN_EDITCHANGE

Cette notification est envoyée lorsque le texte (ou une portion de texte) de l'EDIT a été modifié (soit parce que l'utilisateur a sélectionné un élément, soit parce qu'il a entré du texte).

Cette notification est envoyée après que le contrôle ait été redessiné.

CBN_EDITUPDATE

Cette notification est envoyée lorsque le texte (ou une portion de texte) de l'EDIT a été modifié (soit parce que l'utilisateur a sélectionné un élément, soit parce qu'il a entré du texte).

Cette notification est envoyée avant que le contrôle ait été redessiné.

CBN_SELCHANGE

Cette notification est envoyée lorsque la sélection en cours de la liste a changé.

CBN_SELENDCANCEL

Cette notification est envoyée lorsque l'utilisateur clique sur un élément, puis clique sur un autre contrôle, annulant alors la sélection.

CBN_SELENDOK

Cette notification est envoyée lorsque l'utilisateur clique sur un élément, puis ferme la liste, acceptant alors la sélection.

Messages

Ces messages peuvent être envoyés au contrôle avec la fonction :

SendMessage(hCtl,iCode,wParam,lParam)

Pour chaque message, je décrirai le rôle et la signification de wParam et lParam et de la valeur retournée.

CB_ADDSTRING

Ajoute un élément à la liste (une chaîne de caractères).

  • wParam : 0.

  • lParam : pointeur vers la chaîne de caractères.

  • Valeur de retour : index de l'élément ajouté (relatif à 0).

CB_DELETESTRING

Supprime un élément de la liste (une chaîne de caractères).

  • wParam : index de l'élément (relatif à 0).

  • lParam : 0.

  • Valeur de retour : le nombre d'éléments restants dans la liste, ou CB_ERR en cas d'erreur.

CB_FINDSTRING

Ce message permet de rechercher un élément qui commence par une chaîne de caractères spécifiée.

  • wParam : spécifie l'index du précédent élément trouvé (relatif à 0), ou -1 pour commencer du début.

  • lParam : pointeur sur une chaîne de caractères qui contient les caractères à rechercher.

  • Valeur de retour : index (relatif à 0) de l'élément trouvé, ou CB_ERR si aucun.

CB_FINDSTRINGEXACT

Ce message permet de rechercher une chaîne de caractères qui correspond exactement à celle spécifiée.

  • wParam : spécifie l'index du précédent élément trouvé (relatif à 0), ou -1 pour commencer du début.

  • lParam : pointeur sur la chaîne de caractères à rechercher.

  • Valeur de retour : index (relatif à 0) de l'élément trouvé, ou CB_ERR si aucun.

CB_GETCOUNT

Ce message permet de connaître le nombre d'éléments qu'il y a dans la liste.

  • wParam : 0.

  • lParam : 0.

  • Valeur de retour : le nombre d'éléments, ou CB_ERR en cas d'erreur

CB_GETCURSEL

Ce message permet de connaître l'index de l'élément sélectionné.

  • wParam : 0.

  • lParam : 0.

  • Valeur de retour : index de l'élément sélectionné (relatif à 0), ou CB_ERR si aucun.

CB_GETDROPPEDSTATE

Ce message permet de savoir si la liste est déroulée ou non.

  • wParam : 0.

  • lParam : 0.

  • Valeur de retour : TRUE si elle déroulée, et FALSE sinon.

CB_GETITEMDATA

Ce message permet de récupérer la valeur associée à un élément de la liste par le message CB_SETITEMDATA.

  • wParam : index de l'élément (relatif à 0).

  • lParam : 0.

  • Valeur de retour : valeur associée, ou CB_ERR en cas d'erreur.

CB_GETLBTEXT

Ce message permet de récupérer le texte d'un élément.

  • wParam : index de l'élément (relatif à 0).

  • lParam : pointeur vers la chaîne de caractères qui reçoit le texte.

  • Valeur de retour : longueur du texte (sans le caractère nul), ou CB_ERR en cas d'erreur.

Utilisez le message CB_GETLBTEXTLEN pour récupérer la longueur du texte, et ainsi éviter les débordements.

CB_GETLBTEXTLEN

Ce message permet de récupérer la longueur du texte d'un élément.

  • wParam : index de l'élément (relatif à 0).

  • lParam : 0.

  • Valeur de retour : longueur du texte (sans le caractère nul), ou CB_ERR en cas d'erreur.

CB_INSERTSTRING

Ce message permet d'insérer une chaîne de caractères dans la liste.

  • wParam : index de la position à laquelle insérer la chaîne, ou -1 pour l'ajouter à la fin de la liste.

  • lParam : pointeur vers la chaîne de caractères.

  • Valeur de retour : index de l'élément inséré, ou CB_ERR/CB_ERRSPACE en cas d'erreur.

CB_LIMITTEXT

Ce message permet de limiter le texte écrit par l'utilisateur dans le contrôle EDIT.

  • wParam : nombre maximum de caractères (caractère nul non compris).

  • lParam : 0.

  • Valeur de retour : TRUE

CB_RESETCONTENT

Ce message permet de supprimer tous les éléments de la liste.

  • wParam : 0.

  • lParam : 0.

  • Valeur de retour : CB_OKAY

CB_SETCURSEL

Ce message permet de définir l'élément sélectionné.

  • wParam : index de l'élément (relatif à 0), ou -1 pour n'en sélectionner aucun et effacer le contrôle EDIT.

  • lParam : 0.

  • Valeur de retour : index de l'élément sélectionné, ou CB_ERR en cas d'erreur.

CB_SETITEMDATA

Ce message permet d'associer une valeur à un élément de la liste.

  • wParam : index de l'élément (relatif à 0).

  • lParam : valeur à associer.

  • Valeur de retour : CB_ERR en cas d'erreur.

CB_SHOWDROPDOWN

Ce message permet de montrer où cacher la liste.

  • wParam : TRUE pour afficher la liste, et FALSE pour la cacher.

  • lParam : 0.

  • Valeur de retour : TRUE

Exemple

Ceci est le code complet d'un exemple utilisant la combobox :

#include <windows.h>
#include <stdio.h>

HINSTANCE instance;

#define ID_COMBOBOX_1       110
#define ID_BUTTON_1         111
#define ID_BUTTON_2         112
#define ID_EDIT_1           113

HWND hComboBox1;
HWND hEdit1;

VOID RemplieFenetrePrincipale(HWND fenetrePrincipale)
{
    hComboBox1=CreateWindow(
        "COMBOBOX",
        "",
        WS_CHILD|WS_VISIBLE|CBS_DISABLENOSCROLL|CBS_DROPDOWNLIST,
        10,10,
        180,150,
        fenetrePrincipale,
        (HMENU)ID_COMBOBOX_1,
        instance,
        NULL);

     hEdit1=CreateWindow(
        "EDIT",
        "Entrez le texte à ajouter",
        WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL|WS_BORDER,
        10,40,
        180,20,
        fenetrePrincipale,
        (HMENU)ID_EDIT_1,
        instance,
        NULL);

    CreateWindow(
        "BUTTON",
        "Ajouter",
        WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
        10,70,
        70,20,
        fenetrePrincipale,
        (HMENU)ID_BUTTON_1,
        instance,
        NULL);

    CreateWindow(
        "BUTTON",
        "Supprimer",
        WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
        90,70,
        70,20,
        fenetrePrincipale,
        (HMENU)ID_BUTTON_2,
        instance,
        NULL);
}

VOID NotificationControle(HWND fenetrePrincipale,UINT message,WPARAM wParam, LPARAM lParam)
{
    UINT iId=LOWORD(wParam);
    UINT iCode=HIWORD(wParam);
    HWND hCtl=(HWND)lParam;

   if(iId==ID_COMBOBOX_1)
   {

   }
   else if(iId==ID_BUTTON_1)
   {
        CHAR lpText[256];
        GetWindowText(hEdit1,lpText,256);

        SendMessage(hComboBox1,CB_ADDSTRING,0,(LPARAM)lpText);
   }
   else if(iId==ID_BUTTON_2)
   {
       LRESULT lRes=SendMessage(hComboBox1,CB_GETCURSEL,0,0);
       if(lRes==CB_ERR)
       {
           MessageBox(fenetrePrincipale,"Aucun élément sélectionné","Action annulée",MB_OK);
           return ;
       }

       SendMessage(hComboBox1,CB_DELETESTRING,(WPARAM)lRes,0);
   }
}

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND boutons[2] = {NULL};

    switch (message)
    {
        case WM_CREATE:
            RemplieFenetrePrincipale(fenetrePrincipale);
            return 0;
        case WM_COMMAND:
            NotificationControle(fenetrePrincipale,message,wParam,lParam);
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
            return DefWindowProc(fenetrePrincipale,message,wParam,lParam);
    }
}

int APIENTRY WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
             LPSTR lignesDeCommande, int modeDAffichage)
{
    HWND fenetrePrincipale;
    MSG message;
    WNDCLASS classeFenetre;

    instance = cetteInstance;

    classeFenetre.style = CS_DBLCLKS;// pour attraper les double-clics
    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
    classeFenetre.cbClsExtra = 0;
    classeFenetre.cbWndExtra = 0;
    classeFenetre.hInstance = NULL;
    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    classeFenetre.lpszMenuName = NULL;
    classeFenetre.lpszClassName = "classeF";

// On prévoit quand même le cas où ça échoue
    if(!RegisterClass(&classeFenetre))
        return FALSE;

    fenetrePrincipale=CreateWindow(
        "classeF",
        "Ma premiere fenetre winAPI !",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,CW_USEDEFAULT,
        210,140,
        NULL,
        NULL,
        cetteInstance,
        NULL);

    if (!fenetrePrincipale)
        return FALSE;

    ShowWindow(fenetrePrincipale,modeDAffichage);
    UpdateWindow(fenetrePrincipale);

    while(GetMessage(&message,NULL,0,0))
    {
        TranslateMessage(&message);
        DispatchMessage(&message);
    }

    return message.wParam;
}

Rédacteur : pamaury

La liste de choix

Une list-Box (liste de choix en français) est un contrôle qui contient une liste d'items que l'utilisateur peut choisir comme dans un menu déroulant. On utilise ce contrôle quand l'application doit présenter une liste de noms, des noms de fichiers par exemple, parmi lesquels l'utilisateur fait un choix. Il sélectionne une chaîne en cliquant dessus. La sélection est en surbrillance, et une notification est envoyée à la fenêtre parent.

Création

Pour créer une list-box, il faut appeler CreateWindow en spécifiant "LISTBOX" dans la classe. Ou bien dans un fichier ressource, on écrira par exemple :

CONTROL "", ID_NOMDELALISTBOX, "LISTBOX", WS_CHILD | WS_VISIBLE, x, y, largeur, hauteur

Vous pouvez aussi utiliser le mot-clé LISTBOX.

Néanmoins, cela ne suffit pas, car comme vous vous en doutez, nous avons plusieurs possibilités pour créer notre contrôle. Ce qui va nous intéresser est donc l'attribut de style. En plus de spécifier les styles habituels (WM_CHILD,WM_VISIBLE), vous pouvez spécifier un ou plusieurs de ces styles spécifiques qui commencent tous par "LSB_":

  • LBS_COMBOBOX : notifie à la list-box qu'elle fait partie d'une combo-box (contrôle formé par la réunion d'une list-box et d'un contrôle d'édition). Ceci permet la coordination entre les deux contrôles, de telle sorte qu'ils présentent une interface utilisateur unifiée (UI). La combo-box doit définir ce style. Si ce style est spécifié par la list-box, une erreur en résultera.

  • LBS_DISABLENOSCROLL: affiche une barre verticale de défilement, désactivée quand la boîte ne contient pas assez d'items pour les faire défiler. Si vous ne définissez pas ce style, la barre de défilement sera cachée quand la list-box ne contiendra pas assez d'éléments.

  • LBS_EXTENDEDSEL : permet la sélection de plusieurs items, en utilisant la touche SHIFT et la souris, ou une combinaison de touches particulières.

  • LBS_HASSTRINGS : spécifie que la list-box contient des items constitués de chaînes de caractères. La list-box conserve la mémoire et les adresses des chaînes de caractères. Ainsi, l'application peut utiliser le message MB_GETTEXT pour rechercher le texte pour un item particulier. Par défaut, toutes les list-box ont ce style, à l'exception des list-box qui se redessinent elles-mêmes. Vous pouvez créer une list-box qui se redessine soit avec ce style, soit sans ce style.

  • LBS_MULTICOLUMN : définit une list-box multi-colonnes qui défile horizontalement. Le message LB_SETCOLUMNWIDTH définit la largeur des colonnes.

  • LBS_MULTIPLESEL : une chaîne est sélectionnée à chaque clic. L'utilisateur peut sélectionner plusieurs chaînes.

  • LBS_NODATA : définissez ce style quand le nombre d'items dans la list-box dépasse une centaine. Une list-box no-data doit aussi avoir le style LBS_OWNERDRAWFIXED, mais ne doit pas avoir le style LBS_SORT ou LBS_HASSTRINGS.
    Cette list-box ressemble à une list-box qui se redessine, sauf qu'elle ne contient pas de donnée (data), de chaîne ou de bitmap pour un item. Les commandes pour ajouter, insérer, ou supprimer un item ignorent toujours toute donnée spécifique de l'item ; une requête pour trouver une chaîne à l'intérieur de la list-box échoue toujours. Le système envoie le message WM_DRAWITEM à la fenêtre propriétaire quand un item doit être dessiné. Le membre itemID de la structure DRAWITEMSTRUCT passé avec le message WM_DRAWITEM définit le numéro de ligne de l'item à redessiner. Une list-boxno-data ne peut pas envoyer un message WM_DELEITEM.

  • LBS_NOINTEGRALHEIGHT : définit si la taille de la list-box est exactement la taille spécifiée par l'application quand elle l'a créée. Normalement, le système redimensionne la list-box pour qu'elle n'affiche pas d'item partiellement.

  • LBS_NOREDRAW : spécifie que l'apparence de la list-box n'est pas remise à jour après une modification. Pour changer le mode de mise à jour, utilisez le message WM_SETREDRAW.

  • LBS_NOSEL : spécifie que la list-box contient des items qui peuvent être vus mais pas sélectionnés.

  • LBS_NOTIFY : notifie la fenêtre parent à l'aide d'un message d'entrée chaque fois que l'utilisateur clique ou double-clique sur une chaîne dans la list-box.

  • LBS_OWNERDRAWFIXED : spécifie que le "propriétaire" de la list-box gère la réactualisation de son contenu et veille à ce que les items de la list-box conservent la même hauteur. La fenêtre propriétaire reçoit le message WM_MEASUREITEM quand la list-box est créée, et le message WM_DRAWITEM quand l'aspect visuel de la list-box a changé.

  • LBS_OWNERDRAWVARIABLE : spécifie que le propriétaire de la list-box est responsable de l'actualisation de son contenu, et que les items dans la list-box ont une hauteur variable. La fenêtre propriétaire reçoit le message WM_MEASUREITEM pour chaque item dans une combo-box quand la combo-box est créée, et WM_DRAWITEM quand l'aspect visuel de la combo-box change.

  • LBS_SORT : met les chaînes de la list-box dans l'ordre alphabétique.

  • LBS_STANDARD : met les chaînes de la list-box dans l'ordre alphabétique. La fenêtre parent reçoit un message d'entrée chaque fois que l'utilisateur clique ou double clique sur une chaîne. La list-box a des bords sur tout son pourtour.

Les notifications

L'utilisateur envoie des requêtes d'édition en utilisant le clavier ou la souris. Le système envoie chaque requête à la fenêtre parent du contrôle dans le message WM_COMMAND.
J'ai considéré que vous savez comment interagir avec ce message, et je décrirai donc ici uniquement les codes de notification.

LBN_DBLCLK

Notification envoyée par la list-box quand l'utilisateur double-clique sur une chaîne dans la list-box.

LBN_SELCANCEL

Une application envoie cette notification quand l'utilisateur annule la sélection dans une list-box.

LBN_SELCHANGE

Une application envoie cette notification quand la sélection dans une list-box vient de changer.

LBN_SETFOCUS

Une application envoie cette notification quand la list-box reçoit le focus clavier.

Parmi les notifications, il existe également des messages de types WM_, qui seront reçus par la procédure de la fenêtre parent.

WM_CTLCOLORLISTBOX

Ce message est envoyé à la fenêtre parent de la list-box avant que le système ne dessine la list-box. En répondant à ce message, la fenêtre parent peut définir les couleurs du texte et du fond de la list-box en utilisant le dispositif de handle contextuels spécifique.
La list-box sera coloriée avec la brosse retournée par ce message.

Les messages

Ces messages peuvent être envoyés au contrôle avec la fonction :

SendMessage(hCtl,iCode,wParam,lParam)

Pour chaque message, je décrirai le rôle et la signification de wParam, de lParam et de la valeur retournée.

LB_ADDFILE

L'application envoie ce message pour ajouter le nom de fichier spécifique à la list-box qui contient une liste de répertoires.

  • wparam : 0

  • lParam : pointeur sur un buffer qui définit le nom du fichier à ajouter.

  • Valeur retournée : index du fichier ajouté dont la série commence à 0, ou LB_ERR si une erreur survient.

    • La list-box à laquelle on va ajouter le paramètre lParam doit avoir été remplie à l'aide de la fonction DlgDirList.

LB_ADDSTRING

Une application envoie ce message pour ajouter une chaîne dans la list-box. Si la list-box n'a pas le style LBS_SORT, la chaîne est ajoutée à la fin de la liste. Autrement, la chaîne est insérée dans la liste, et la liste est réorganisée.

  • wparam : 0

  • lParam : pointeur sur une chaîne terminée par un caractère nul qui sera ajouté.

  • Valeur retournée : index d'origine 0 de la chaîne dans la list-box signifie que le message a réussi. LB_ERR indique qu'une erreur est survenue. LB_ERRSPACE indique qu'il n'y a pas assez de place pour stocker la nouvelle chaîne.

LB_DELETESTRING

Ce message est envoyé par l'application pour supprimer une chaîne dans la list-box.

  • wparam : index d'origine 0 de la chaîne dans la list-box.

  • lParam : 0

  • Valeur retournée : le nombre de chaînes supprimées dans la liste signifie que l'opération a réussi. LB_ERR indique que l'index spécifié est un index supérieur au nombre d'items de la liste.

LB_FINDSTRING

Ce message est envoyé par l'application pour trouver la première chaîne dans la list-Box contenant le préfixe précisé.

  • wparam : index, faisant partie d'une suite commençant par zéro, de l'item placé avant le premier item recherché. Quand la recherche atteint la fin de la liste, elle se poursuit en revenant au début de la list-box jusqu'à l'item spécifié par le paramètre d'index de départ. Si l'index de départ est -1, la recherche va du début à la fin de la list-box.

  • lParam : pointeur sur une chaîne à terminaison nulle qui contient le préfixe recherché. La recherche est indépendante de la case ; ainsi, cette chaîne peut contenir n'importe quelle combinaison de lettres majuscules ou minuscules.

  • Valeur retournée : si c'est l'index de l'item associé, le message a réussi. LB_ERR indique que la recherche est infructueuse.

LB_FINDSTRINGEXACT

Ce message est envoyé par une application pour trouver la première chaîne associée à la chaîne précisée dans le paramètre lParam.

  • wparam : index (série d'origine 0) de l'item situé avant le premier item où commence la recherche. Quand la recherche atteint la fin de la liste, elle se poursuit en revenant au début de la list-box jusqu'à l'item spécifié par le paramètre d'index de départ. Si l'index de départ est -1, la recherche va du début à la fin de la list-box.

  • lParam : pointeur sur une chaîne à terminaison nulle contenant le nom complet du fichier recherché avec son extension. La recherche est indépendante de la case ; ainsi, cette chaîne peut contenir n'importe quelle combinaison de lettres majuscules ou minuscules.

  • Valeur retournée : si c'est l'index de l'item associé, le message a réussi. LB_ERR indique que la recherche est infructueuse.

LB_GETCARETINDEX

Ce message est envoyé par une application pour déterminer l'index de l'item ayant le focus dans une list-box à sélection multiple. L'item peut ou ne peut pas être sélectionné.

  • wparam : 0

  • lParam : 0

  • Valeur retournée : index de l'item de la list-box ayant le focus. Si la list-box est une list-box à sélection unique, la valeur est l'index de l'item sélectionné, s'il y en a un.

LB_GETCOUNT

Ce message est envoyé par l'application pour rechercher le nombre d'items qu'il y a dans une list-box.

  • wparam : 0

  • lParam : 0

  • Valeur retournée : nombre d'items dans la list-box si le message réussit. LB_ERR indique qu'une erreur s'est produite.

LB_GETCURSEL

Ce message est envoyé par une application pour recherche l'index de l'item sélectionné, s'il y en a un, dans une list-box à sélection unique.

  • wparam : 0

  • lParam :0

  • Valeur retournée : dans une list-box à une seule sélection, cette valeur est l'index de l'item sélectionné. S'il n'y a pas de sélection, la valeur retournée est LB_ERR.

Vous ne devez pas envoyer ce message à une list-box à sélection multiple.

LB_GETITEMDATA

Ce message est envoyé par une application pour trouver la valeur 32 bit définie par l'application, et associée à l'item spécifié de la list-box.

  • wparam : index de l'item

  • lParam : 0

  • Valeur retournée : valeur 32 bits associée à l'item, ou LB-ERR si une erreur est survenue. Si l'item est dans une list-box avec une auto-remise à jour, et qui a été créée sans le style LBS_HASSTRING, cette valeur 32 bits sera dans le paramètre lParam des messages LB_ADDSTRING ou LB_INSERTSTRING, qui ajoutent un item à la list-box. Autrement, c'est la valeur de lParam du message LB_SETITEMDATA.

LB_GETITEMRECT

Ce message est envoyé par l'application pour rechercher les dimensions du rectangle qui limite un item de la list-box quand il est normalement affiché dans la list-box.

  • wparam : index de l'item

  • lParam : pointeur sur une structure RECT qui reçoit les coordonnées clientes de l'item dans la list-box.

  • Valeur retournée : LB_ERR s'il y a une erreur.

LB_GETSELITEMS

Ce message est envoyé par une application pour remplir un buffer avec un tableau d'entiers (integer) qui définit les numéros des items dans la sélection d'items d'une list-box à sélection multiple.

  • wparam : nombre maximum d'items sélectionnés dont les numéros seront placés dans le buffer.

  • lParam : pointeur sur le buffer assez grand pour contenir le nombre d'entiers défini dans le paramètre lParam.

  • Valeur retournée : nombre d'items placés dans le buffer. Si la list-box est à sélection unique, la valeur retournée est LB_ERR. Ce message retourne aussi LB_ERR si vous passez des paramètres invalides à une list-box à sélection multiple.

LB_GETSELCOUNT

Ce message est envoyé par une application pour rechercher le nombre total d'items sélectionnés dans une list-box à sélection multiple.

  • wparam : 0

  • lParam : 0

  • Valeur retournée : nombre d'items sélectionnés dans la list-box si le message réussit. LB_ERR si la list-box est à sélection unique.

LB_GETTEXT

Ce message est envoyé par une application pour rechercher une chaîne dans une list-box.

  • wparam : index de la chaîne cherchée.

  • lParam : pointeur sur un buffer qui reçoit la chaîne. Le buffer doit avoir une taille suffisante pour contenir la chaîne avec un caractère nul de terminaison. Un message LB_GETTEXTLEN peut être envoyé après LB_GETTEXT pour rechercher la longueur, en caractères, de la chaîne.

  • Valeur retournée : longueur de la chaîne, en caractères sans le caractère nul de terminaison si le message réussit. LB_ERR indique que l'index n'est pas un index valide.

LB_GETTEXTLEN

Une application envoie le message LB_GETTEXTLEN pour retrouver la longueur d'une chaîne dans la list-box.

  • wparam : index (origine 0) de la chaîne.

  • lParam : 0

  • Valeur retournée : longueur de la chaîne, en CHAR, en excluant le caractère nul terminal. Sous certaines conditions, cette valeur peut être supérieure à la longueur du texte.
    Si wParam ne définit pas un index valide, cette valeur est LB_ERR.

    • Pour obtenir à tous les coups l'exacte longueur d'un texte, utilisez les messages WM_GETTEXT, LB_GETTEXT, ou CB_GETLBTEXT , ou la fonction GetWindowText.

LB_INSERTSTRING

Une application envoie ce message pour insérer une chaîne dans une list-box. Différemment du message LB_ADDSTING, ce message ne produit pas de liste que l'on pourrait trier avec le style LBS_SORT.

  • wparam : définit l'index (d'origine 0) de la position où il faut insérer l'item. Si ce paramètre est 1, la chaîne sera ajoutée à la fin de la liste.

  • lParam : pointeur sur la chaîne à terminaison nulle que l'on veut insérer.

  • Valeur retournée : index de la position à laquelle la chaîne doit être insérée. Si une erreur survient, la valeur retournée est LB_ERR. S'il n'y a pas assez de place pour stocker la nouvelle chaîne, la valeur retournée sera LB_ERRSPACE.

LB_RESETCONTENT

L'application envoie ce message pour supprimer tous les items d'une list-box.

  • wparam : 0

  • lParam : 0

  • Valeur retournée : ne retourne pas de valeur.

LB_SETCURSEL

Une application envoie ce message pour sélectionner une chaîne et la faire apparaître en la faisant défiler dans la zone visible, si nécessaire. Quand la nouvelle chaîne est sélectionnée, la list-box supprime la surbrillance de la chaîne sélectionnée préalablement.

  • wparam : définit l'index (d'origine 0) de la chaîne sélectionnée. Si ce paramètre est -1, la list-box est définie comme n'ayant pas de sélection.

  • lParam : 0

  • Valeur retournée : si une erreur survient, la valeur est LB_ERR. Si wParam est -1, la valeur retournée est LB_ERR même s'il n'y a pas eu d'erreur.

N'utilisez ce message qu'avec une list-box à sélection unique. Vous ne pouvez pas utiliser ce message pour définir ou annuler une sélection dans une list-box à sélection multiple.

LB_SETHORIZONTALEXTENT

Une application envoie ce message pour définir la distance de déplacement horizontal (scrolling), en pixels, d'une list-box. Si la largeur de la list-box est inférieure à cette valeur, la barre horizontale fait défiler les items horizontalement. Si la largeur de la list-box est égale ou supérieure à cette valeur, la barre horizontale de défilement est cachée.

  • wparam : définit le nombre de pixels de déplacement de la list-box.

  • lParam : 0

  • Valeur retournée : pas de valeur retournée.

La list-box doit avoir le style WS_HSCROLL pour pouvoir utiliser ce message.

LB_SETITEMDATA

Une application envoie ce message pour définir une valeur associée à l'item spécifié d'une list-box.

  • wparam : définit l'index (d'origine 0) de l'item. Si cette valeur est -1, la valeur de lParam sera appliquée à tous les items de la list-box.

  • lParam : définit la valeur à associer à l'item.

  • Valeur retournée : si une erreur survient, LB_ERR.

LB_SETSEL

Une application envoie ce message pour sélectionner une chaîne dans une list-box à sélection multiple.

  • wparam : précise comment le sélection va être définie. Si ce paramètre est TRUE, la chaîne est sélectionnée et mise en surbrillance ; si ce paramètre est FALSE, la surbrillance est annulée et la chaîne n'est pas sélectionnée.

  • lParam : définit l'index (d'origine 0) de la chaîne choisie. Si ce paramètre est 1, la sélection est ajoutée ou annulée de toutes les chaînes, en fonction du paramètre wParam.

  • Valeur retournée : LB_ERR en cas d'erreur.

Exemple

#include <windows.h>
#include <stdio.h>

#define ID_BOUTON1      110
#define ID_BOUTON2      111

//variables globales
HINSTANCE inst;
HWND hListBox[2]; //handle des deux contrôles d'édition

//Afficher une list-Box et la remplir
HWND afficheListBox (HWND hwnd)
{
HWND hListBox=CreateWindow(
                        "LISTBOX","",
                         WS_VISIBLE|WS_CHILD|WS_BORDER |LBS_NOTIFY,
                         50,50,150,200,hwnd,NULL,inst,NULL);

//Entrer des items dans la list-box
SendMessage (hListBox, LB_ADDSTRING ,0,(LPARAM)"Première entrée");
SendMessage (hListBox, LB_ADDSTRING ,0,(LPARAM)"Deuxième entrée");
SendMessage (hListBox, LB_ADDSTRING ,0,(LPARAM)"Troisième entrée");
SendMessage (hListBox, LB_ADDSTRING ,0,(LPARAM)"Quatrième entrée");

return hListBox;
}

//Boutons pour la démo
HWND boutonMessage(HWND hwnd,HMENU code,long x, long y, char * texte)
{
HWND hBouton=CreateWindow(
                        "BUTTON",
                        texte,
                        WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
                        x,y,
                        350,30,
                        hwnd,
                        code,
                        inst,
                        NULL);
return hBouton;
}

LRESULT CALLBACK  Procedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{

    static HWND hBouton[1]={0};
    char texte[5]={0},texte2[50]={0};
switch (message)

    {
        //afficher les contrôles d'édition à la réception du message WM_CREATE
        case WM_CREATE:
               hListBox[0]=afficheListBox (hwnd);
               //afficher les boutons pour la démo
               hBouton[0]= boutonMessage(hwnd, (HMENU)ID_BOUTON1,220,90,"Message LB_GETCURSEL: item sélectionné ");
        return 0;

        case WM_COMMAND:

          switch (LOWORD(wParam))
          {
              //test du message LB_GETCURSEL
              case ID_BOUTON1 :
               //Trouver le numéro de la ligne sélectionnée, et transformer le résultat en chaîne
               sprintf(texte,"%d",(SendMessage(hListBox[0],LB_GETCURSEL,0,0)+1));
               //Afficher une boîte de dialogue
               //s'il y a une sélection
              if (SendMessage(hListBox[0],LB_GETCURSEL,0,0)!= LB_ERR)
              {
                strncat(texte2,"Vous avez sélectionné la ligne ",50);
                strncat(texte2,texte,50);
              }
              else //pas de sélection
              {
              strncat(texte2,"Vous n'avez sélectionné aucune ligne." ,50);
              }
              MessageBox(hwnd,(LPCTSTR )texte2,"",MB_OK|MB_ICONWARNING);
              break;
              //ajoutez d'autres tests et d'autres boutons si vous le voulez
              //case ID_BOUTON2:
              //autre test
              //breaks;
          }

           switch (HIWORD(wParam))
          {
              case LBN_SELCHANGE :
                char temp[256] = "";
                SendMessage(hListbox, LB_GETTEXT, SendMessage(hListbox[0], LB_GETCURSEL, 0, 0), temp);
                MessageBox(hwnd,temp, "Item sélectionné :", MB_ICONINFORMATION);
              break;
          }
              return 0;

        case WM_CLOSE:
             DestroyWindow(hwnd);
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, message, wParam, lParam);
        }
}

int WINAPI WinMain(HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
                                        LPSTR lignesDeCommande, int modeDAffichage)
{
       //Variables de la fonction principale
    MSG msg;
    WNDCLASS wc;
        HWND hwnd;
        inst = cetteInstance;
        // Structure de la classe de la fenêtre principale
    wc.style = 0 ;
    wc.lpfnWndProc = Procedure;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = cetteInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground =  (HBRUSH)(1+ COLOR_BTNFACE);
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "ClassePrincipale";

        //Enregistrer la classe de fenêtre
    if(!RegisterClass(&wc)) return FALSE;
    hwnd = CreateWindow("ClassePrincipale", "Démo du contrôle List-Box",WS_OVERLAPPEDWINDOW   ,
                              200,100,600 ,300, NULL, NULL, cetteInstance, NULL);
        if (!hwnd)  return FALSE;
    ShowWindow(hwnd,SW_SHOW);
        UpdateWindow( hwnd );

        //Boucle de message
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

Rédactrice : acryline

La glissière

Le slider, ou trackbar, (glissière en français) est très utilisé dans la plupart des programmes d'aujourd'hui. Vous l'avez sûrement utilisé sans même savoir ce qui se passait derrière, chose que je propose de faire ici. :p

Il est loin d'être particulièrement difficile, et je ne vous cacherai pas que sa préparation est plus l'objet d'erreurs que son utilisation. En effet, il fait partie de la Common Controls Library, ou bibliothèque des contrôles communs, que vous devrez intégrer et lier au projet.

Préparation préalable

Intégration

Afin de déclarer le slider, ou plutôt sa classe, vous aurez besoin d'insérer dans votre projet :

#include <commctrl.h>
Linker

Et en ce qui concerne le linker, vous devrez ajouter l'entrée -lcomctl32, ou sous C::B comctl32.

En bref, rien de bien sorcier. La seule source d'erreurs est une potentielle faute d'orthographe. ;)

Création

Concernant sa création, vous pouvez passer comme d'habitude par CreateWindow, ou par les template de ressource. Le mot-clé à utiliser est alors CONTROL. Je vous l'avais dit qu'il était générique... c'est le mot-clé se rapprochant le plus de CreateWindow. Mais dans les deux cas, le nom de classe à utiliser est TRACKBAR_CLASS. En plus de spécifier cette constante prédéfinie au 2ème argument de CreateWindow (ou au 3ème de CONTROL), vous pouvez utiliser un ou plusieurs, par associations de flag, des styles suivants :

  • TBS_AUTOTICKS : le slider comporte une marque (trait) pour chaque palier d'incrémentation déterminé à partir de ses marges.

  • TBS_BOTH : le slider comporte des marques sur ses deux côtés.

  • TBS_BOTTOM : le slider comporte des marques seulement en-dessous de lui.

    Style valide uniquement avec un slider horizontal.

  • TBS_DOWNISLEFT : utilisé avec un slider vertical, il permet d'inverser le sens d'incrémentation.

  • TBS_FIXEDLENGTH : permet au curseur de changer sa taille en recevant le message TBS_SETTHUMBLENGTH.

  • TBS_HORZ : style par défaut ; le slider est orienté horizontalement.

  • TBS_LEFT : le slider comporte des marques sur son côté gauche.

    Style valide uniquement quand son orientation est verticale.

  • TBS_NOTHUMB : le slider ne comporte pas de curseur.

  • TBS_NOTICKS : le slider ne comporte pas de marques.

  • TBS_REVERSED : le slider est inversé ; son sens d'incrémentation est alors de la droite vers la gauche.

    Supporté uniquement par la version 5.80 ou ultérieure.

  • TBS_RIGHT : même effet que TBS_LEFT, mais sur le côté droit.

    Style valide uniquement quand son orientation est verticale.

  • TBS_TOOLTIPS : le slider supporte les Tooltip, ou ballons d'informations. Sa position pourra alors être changée avec TBM_SETTIPSIDE.

    Supporté uniquement par la version 4.70 ou ultérieure.

  • TBS_TOP : le slider comporte des marques au-dessus de lui.

    Style valide uniquement quand son orientation est horizontale.

  • TBS_VERT : le slider est alors orienté verticalement.

Il est évident que vous ne devez pas combiner des styles contradictoires, comme des orientations contraires...

Sa création étant maintenant démystifiée, passons maintenant aux notifications que ce contrôle peut envoyer, et ensuite aux messages qu'il peut recevoir et traiter.

Notifications

Je vais décrire ici les notifications envoyées par le contrôle via le message WM_COMMAND (sauf mention contraire). Il est assumé que vous savez comment interagir avec ce message, et je décrirai donc ici uniquement les codes de notifications.

Les notifications WM_HSCROLL et WM_VSCROLL ne transitent pas par WM_COMMAND, elles sont traitées en tant que tel à part (comme on traite WM_COMMAND).

Je vous précise au préalable que le slider envoie, à l'instar du contrôle static, une notification WM_CTLCOLORSTATIC pour colorer son pourtour. Vous pourrez alors changer sa couleur de fond (cf "Le contrôle Static").

WM_HSCROLL ou WM_VSCROLL

Envoyés à la procédure de la fenêtre parent, ces notifications indiquent un drag du curseur par l'utilisateur :
- horizontal, pour un slider orienté horizontalement
- vertical, pour un slider orienté verticalement.

Le mot bas du paramètre wParam contient le code de notification.

TB_THUMBTRACK

Cette notification est envoyée lorsque l'utilisateur bouge le curseur.

Le mot haut du paramètre wParam contient la position changée du curseur.

TB_THUMBPOSITION

Cette notification est envoyée lorsque l'utilisateur a bougé le curseur, puis relâché la souris (équivaut donc à TB_THUMBTRACK + WM_LBUTTONUP).

Le mot haut du paramètre wParam contient la position changée du curseur.

Messages

Ces messages peuvent être envoyés au contrôle avec la fonction :

SendMessage(hCtl,iCode,wParam,lParam)

Pour chaque message je décrirai le rôle et la signification de wParam et lParam, ainsi que la valeur retournée.

TBM_CLEARSEL

Ce message permet de nettoyer la sélection en cours.

  • wParam : (BOOL) s'il vaut TRUE, redessine le slider.

  • lParam : 0.

  • Valeur de retour : aucune.

TBM_CLEARTICS

Ce message permet de supprimer les marques du slider, exceptées la première et la dernière.

  • wParam : (BOOL) s'il vaut TRUE, redessine le slider.

  • lParam : 0.

  • Valeur de retour : aucune.

TBM_GETCHANNELRECT

Ce message permet de récupérer les dimensions du rectangle limite du slider, dans lequel il évolue.

  • wParam : 0.

  • lParam : (LPRECT) adresse d'une variable de type RECT qui va recevoir les dimensions.

  • Valeur de retour : aucune.

TBM_GETPOS

Ce message permet de récupérer la position courante du curseur.

  • wParam : 0.

  • lParam : 0.

  • Valeur de retour : valeur 32 bits représentant la position courante.

TBM_GETRANGEMAX ou TBM_GETRANGEMIN

Ces messages permettent de récupérer la valeur maximale ou minimale que peut prendre le slider (ses bornes).

  • wParam : 0.

  • lParam : 0.

  • Valeur de retour : valeur 32 bits désignant la valeur de la borne spécifiée.

TBM_GETTHUMBLENGTH

Ce message permet de récupérer la longueur du curseur du slider.

  • wParam : 0.

  • lParam : 0.

  • Valeur de retour : longueur du curseur (en pixels).

TBM_GETTHUMBRECT

Ce message permet de récupérer les coordonnées et dimensions du curseur.

  • wParam : 0.

  • lParam : 0.

  • Valeur de retour : (RECT) dimensions et positions du curseur.

TBM_GETTOOLTIPS

Ce message permet de récupérer un handle sur un ballon de message associé au slider s'il y a lieu.

  • wParam : 0.

  • lParam : 0.

  • Valeur de retour : (HWND) handle du tooltip assigné, s'il y en a un. Dans le cas contraire, NULL.

Le style TBS_TOOLTIPS est évidemment nécessaire ici.

TBM_SETPOS

Ce message permet de définir la position courante du curseur dans le slider.

  • wParam : (BOOL) définit l'état de dessin du contrôle : s'il vaut TRUE, le slider sera redessiné après.

  • lParam : (LONG) définit la nouvelle position du curseur, en unités logiques, comprise entre les bornes du slider.

  • Valeur de retour : aucune.

TBM_SETRANGE

Ce message permet de définir les bornes du slider.

  • wParam : (BOOL) définit l'état de dessin du contrôle : s'il vaut TRUE, le slider sera redessiné après.

  • lParam : (LONG) nouvelles bornes, définies grâce à la macro MAKELONG(min, max)

  • Valeur de retour : aucune.

TBM_SETRANGEMAX ou TBM_SETRANGEMIN

Ces messages ont la même fonction que TBM_SETRANGE, mais sur une seule des bornes, spécifiée dans leur nom.

  • wParam : (BOOL) définit l'état de dessin du contrôle : s'il vaut TRUE, le slider sera redessiné après.

  • lParam : (LONG) définit la nouvelle borne (minimale ou maximale).

  • Valeur de retour : aucune.

TBM_SETTHUMLENGTH

Ce message permet de définir la longueur du curseur du slider.

  • wParam : (UINT) longueur du curseur, en pixels.

  • lParam : 0.

  • Valeur de retour : aucune.

TBM_SETTICFREQ

Ce message permet de définir la fréquence des marques.

  • wParam : (WORD) fréquence des marques (1 = chaque palier).

  • lParam : 0.

  • Valeur de retour : aucune.

TBM_SETTIPSIDE

Ce message permet de définir le côté du slider où sera affiché le ballon d'infos.

  • wParam : (int) définit le côté du ballon : peut alors prendre les valeurs TBTS_TOP (haut), TBTS_LEFT (gauche), TBTS_RIGHT (droite), TBTS_BOTTOM (bas).

  • lParam : 0.

  • Valeur de retour : précédente position du ballon.

Utile uniquement avec le style TBS_TOOLTIP.

TBM_SETTOOLTIPS

Ce message permet de définir le ballon d'informations associé au slider.

  • wParam : (HWND) handle d'un ballon déjà créé.

  • lParam : 0.

  • Valeur de retour : aucune.

Exemple

Le présent exemple montre quelques fonctions permettant de créer ou manipuler le slider simplement (à utiliser, non compilable tel quel) :

HWND createSlider (HINSTANCE instance, HWND parentWindow)
{
    return CreateWindow(TRACKBAR_CLASS, "", WM_CHILD | WM_VISIBLE | TBS_NOTICKS, 20, 20, 300, 20, parentWindow, (HMENU)NULL, instance, NULL);
};

void setRange (HWND slider, int min, int max)
{
    SendMessage(slider, TBM_SETRANGE, TRUE, MAKELONG(min,max));

    return;
}

void setPos (HWND slider, int position)
{
    SendMessage(slider, TBM_SETPOS, TRUE, position);

    return;
}

int getPos (HWND slider)
{
    return SendMessage(slider, TBM_GETPOS, 0, 0);
}

Celui-ci est tout bête, mais je ne pense pas que vous vous servirez des autres avant un petit bout de temps. ;)
Rédacteur : Mg++

Le contrôle Static

Ce contrôle est l'un des plus simples et des plus utiles qui soit : il permet d'afficher un libellé de texte, une icône ou une image, mais qui n'interagit pas avec l'utilisateur, d'où son nom.

Création

Pour créer un STATIC, il faut appeler CreateWindow avec comme classe "STATIC", et ajouter en plus des styles classiques (WS_CHILD,WS_VISIBLE) un ou plusieurs de ces styles :

Pour une boîte de dialogue, vous devrez utiliser le mot-clé CONTROL, très pratique, qui est un équivalent template de CreateWindow : son premier paramètre équivaut au 2ème de CreateWindow, son 2ème correspond à son ID, son 3ème à la classe de fenêtre (autrement dit ici STATIC), et son quatrième, ses styles. Les paramètres suivants désignent les dimensions habituelles. Exemple :

//Affiche un static contenant l'image <gras>img</gras> chargée en ressources (cf ci-après)
CONTROL "img", -1, "STATIC", SS_BITMAP, 10, 10, 0, 0

  • SS_BITMAP : spécifie une boîte rectangulaire qui permet d'afficher une image bitmap. La taille du contrôle est déterminée automatiquement en fonction de celle de l'image.

  • SS_BLACKFRAME : spécifie une boîte rectangulaire avec un cadre de la même couleur que celui des fenêtres (noir par défaut).

  • SS_BLACKRECT : spécifie une boîte rectangulaire remplie avec la même couleur que le cadre des fenêtres (noir par défaut).

  • SS_CENTER : spécifie une boîte rectangulaire avec du texte centré. Si le texte est trop long, il est automatiquement réparti en plusieurs lignes.

  • SS_ETCHEDFRAME : dessine le cadre en utilisant l'attribut EDGE_ETCHED.

  • SS_ETCHEDHORZ : dessine les angles du haut et du bas en utilisant l'attribut EDGE_ETCHED.

  • SS_ETCHEDVERT : dessine les angles de gauche et droite en utilisant l'attribut EDGE_ETCHED.

  • SS_GRAYRECT : spécifie une boîte rectangulaire avec un cadre de la couleur en cours du fond d'écran (gris par défaut).

  • SS_GRAYFRAME : spécifie une boîte rectangulaire avec la couleur en cours du fond d'écran (gris par défaut).

  • SS_ICON : spécifie une boîte rectangulaire qui permet d'afficher une icône (ou un curseur animé). La taille du contrôle est déterminée automatiquement en fonction de celle de l'icône.

  • SS_LEFT : spécifie une boîte rectangulaire avec du texte aligné à gauche. Si le texte est trop long, il est automatiquement réparti en plusieurs lignes.

  • SS_LEFTNOWORDWRAP : spécifie une boîte rectangulaire avec du texte aligné à gauche. Si le texte est trop long, il est automatiquement tronqué.

  • SS_NOPREFIX : prévient l'interprétation du signe & dans le texte comme un raccourci clavier.

  • SS_NOTIFY : envoie les notifications STN_CLICKED, STN_DBLCLK, STN_DISABLE et STN_ENABLE à la fenêtre parente.

  • SS_OWNERDRAW : le contrôle doit être dessiné par l'utilisateur.

  • SS_REALSIZEIMAGE : utilisé avec SS_ICON. Au lieu de considérer la taille de la ressource comme taille du contrôle, la taille retournée par LoadImage est utilisée.

  • SS_RIGHT : spécifie une boite rectangulaire avec du texte aligné à droite. Si le texte est trop long, il est automatiquement réparti en plusieurs lignes.

  • SS_RIGHTJUST : utilisé avec SS_ICON ou SS_BITMAP. Lors d'un redimensionnement du contrôle, au lieu de changer la position du coin en bas à droite, c'est la position du coin en haut à gauche qui est modifiée.

  • SS_SIMPLE : spécifie une boîte rectangulaire avec du texte sur une seule ligne. Aucune modification n'est apportée au texte, même si celui-ci est trop long. Le texte n'apparaît pas grisé si le contrôle est désactivé.

  • SS_SUNKEN : dessine un cadre créant l'effet d'un renfoncement dans la fenêtre, autour du contrôle.

  • SS_WHITEFRAME : spécifie une boîte rectangulaire avec un cadre de la même couleur que le fond de la fenêtre (blanc par défaut).

  • SS_WHITERECT : spécifie une boîte rectangulaire remplie avec la même couleur que le fond de la fenêtre.

Notifications

Je vais décrire ici les notifications envoyées par le contrôle via le message WM_COMMAND (sauf mention contraire) . Il est supposé que vous savez comment interagir avec ce message, et je décrirai donc ici uniquement les codes de notifications.

STN_CLICKED

Ce message est envoyé lorsque l'utilisateur clique sur le contrôle.

Vous devez avoir défini le style SS_NOTIFY lors de la création du contrôle.

STN_DBLCLK

Ce message est envoyé lorsque l'utilisateur double-clique sur le contrôle.

Vous devez avoir défini le style SS_NOTIFY lors de la création du contrôle.

STN_DISABLE

Ce message est envoyé lorsque le contrôle est désactivé.

Vous devez avoir défini le style SS_NOTIFY lors de la création du contrôle.

STN_ENABLE

Ce message est envoyé lorsque le contrôle est activé.

Vous devez avoir défini le style SS_NOTIFY lors de la création du contrôle.

WM_CTLCOLORSTATIC

Ce message est envoyé à la fenêtre parent juste avant que le contrôle ne soit dessiné.
Ceci est principalement utile pour changer la couleur du texte, ou du fond du contrôle pour le dessin .

Les paramètres valent alors:

  • wparam : handle du DC sur lequel dessiner.

  • lparam : handle du bouton

  • Valeur de retour : handle d'une brosse qui sera utilisée pour le dessin du fond.

- Ce message est envoyé directement à la fonction de gestion des messages, et non pas via le message WM_COMMAND
- La brosse retournée ne sera pas automatiquement détruite par le système.

Messages

Ces messages peuvent être envoyés au contrôle avec la fonction :

SendMessage(hCtl,iCode,wParam,lParam)

Pour chaque message, je décrirai le rôle et la signification de wParam, lParam et de la valeur retournée.

STM_GETICON

Ce message permet de récupérer le handle de l'icône associée au contrôle.

  • wParam : 0.

  • lParam : 0.

  • Valeur de retour : handle de l'icône associée, ou NULL si aucune.

STM_GETIMAGE

Ce message permet de récupérer le handle de l'image associée au contrôle.

  • wParam : spécifie le type de l'image à récupérer. Ce peut être une de ces valeurs :

    • IMAGE_BITMAP

    • IMAGE_CURSOR

    • IMAGE_ENHMETAFILE

    • IMAGE_ICON

  • lParam : 0.

  • Valeur de retour : handle de l'image associée, ou NULL si aucune.

STM_SETICON

Ce message permet de définir l'icône associée au contrôle.

  • wParam : handle de l'icône.

  • lParam : 0.

  • Valeur de retour : handle de l'icône précédemment associée, ou NULL si aucune.

STM_SETIMAGE

Ce message permet de définir l'image associée au contrôle.

  • wParam : spécifie le type de l'image à définir. Ce peut être une de ces valeurs :

    • IMAGE_BITMAP

    • IMAGE_CURSOR

    • IMAGE_ENHMETAFILE

    • IMAGE_ICON

  • lParam : handle de l'image.

  • Valeur de retour : handle de l'image précédemment associée, ou NULL si aucune.

Exemple

Ceci est le code complet d'un exemple utilisant les STATIC.

#include <windows.h>
#include <stdio.h>

HINSTANCE instance;

#define ID_STATIC_1     110

VOID RemplieFenetrePrincipale(HWND fenetrePrincipale)
{
    HWND hCtl=CreateWindow(
        "STATIC",
        "Double-\ncliquez\nsur moi !!",
        WS_CHILD|WS_VISIBLE|SS_CENTER|SS_NOTIFY,
        10,10,
        100,60,
        fenetrePrincipale,
        (HMENU)ID_STATIC_1,
        instance,
        NULL);

    hCtl=CreateWindow(
        "STATIC",
        "",
        WS_CHILD|WS_VISIBLE|SS_ICON,
        10,80,
        0,0,
        fenetrePrincipale,
        NULL,
        instance,
        NULL);

    HANDLE hIcon=LoadIcon(NULL,IDI_ERROR);
    SendMessage(hCtl,STM_SETICON,(WPARAM)hIcon,0);

    hCtl=CreateWindow(
        "STATIC",
        "",
        WS_CHILD|WS_VISIBLE|SS_ICON,
        120,10,
        0,0,
        fenetrePrincipale,
        NULL,
        instance,
        NULL);

    HANDLE hImage=LoadImage(NULL,MAKEINTRESOURCE(OCR_NORMAL),IMAGE_CURSOR,0,0,LR_SHARED);
    SendMessage(hCtl,STM_SETIMAGE,(WPARAM)IMAGE_CURSOR,(LPARAM)hImage);

    hCtl=CreateWindow(
        "STATIC",
        "",
        WS_CHILD|WS_VISIBLE|SS_BITMAP,
        120,60,
        0,0,
        fenetrePrincipale,
        NULL,
        instance,
        NULL);
    hImage=LoadImage(NULL,MAKEINTRESOURCE(OBM_CHECK),IMAGE_BITMAP,0,0,LR_SHARED);
    SendMessage(hCtl,STM_SETIMAGE,(WPARAM)IMAGE_BITMAP,(LPARAM)hImage);
}

VOID NotificationControle(HWND fenetrePrincipale,UINT message,WPARAM wParam, LPARAM lParam)
{
    UINT iId=LOWORD(wParam);
    UINT iCode=HIWORD(wParam);
    HWND hCtl=(HWND)lParam;

    switch(iId)
    {
        case ID_STATIC_1:
            if(iCode==STN_DBLCLK)
                MessageBox(fenetrePrincipale,"Vous avez double-cliqué sur le premier STATIC","",MB_OK);
            break;
    }
}

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND boutons[2] = {NULL};

    switch (message)
    {
        case WM_CREATE:
            RemplieFenetrePrincipale(fenetrePrincipale);
            return 0;
        case WM_COMMAND:
            NotificationControle(fenetrePrincipale,message,wParam,lParam);
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
            return DefWindowProc(fenetrePrincipale,message,wParam,lParam);
    }
}

int APIENTRY WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
             LPSTR lignesDeCommande, int modeDAffichage)
{
    HWND fenetrePrincipale;
    MSG message;
    WNDCLASS classeFenetre;

    instance = cetteInstance;

    classeFenetre.style = CS_DBLCLKS;// pour attraper les double-clics
    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
    classeFenetre.cbClsExtra = 0;
    classeFenetre.cbWndExtra = 0;
    classeFenetre.hInstance = NULL;
    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    classeFenetre.lpszMenuName = NULL;
    classeFenetre.lpszClassName = "classeF";

// On prévoit quand même le cas où ça échoue
    if(!RegisterClass(&classeFenetre))
        return FALSE;

    fenetrePrincipale=CreateWindow(
        "classeF",
        "Ma premiere fenetre winAPI !",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,CW_USEDEFAULT,
        200,200,
        NULL,
        NULL,
        cetteInstance,
        NULL);

    if (!fenetrePrincipale)
        return FALSE;

    ShowWindow(fenetrePrincipale,modeDAffichage);
    UpdateWindow(fenetrePrincipale);

    while(GetMessage(&message,NULL,0,0))
    {
        TranslateMessage(&message);
        DispatchMessage(&message);
    }

    return message.wParam;
}

Rédacteur : pamaury

Ce sont les principaux contrôles. Les autres vous seront présentés dans un autre chapitre de ce cours. ;)

Les bases L'affichage
Grandes lignes
  1. Les bases
  2. Les contrôles
  3. L'affichage
  4. TP : Un lecteur audio
  5. Les événements
  6. Esthétique avancée

L'auteur

pasquestion etpuisquoiencore


Cours sur L'API Winodos de openclassrooms

L'affichage

Vous allez durant ce chapitre apprendre à gérer l'affichage manuel d'informations dans votre fenêtre : celles ci peuvent être du texte, des formes géométriques (dans ce cas ci on parlera de dessin) ou des images (en général des bitmaps).

Les contextes de périphériques

L'API windows permet de dessiner dans n'importe quel périphérique (imprimante, écran etc.) sans se préoccuper de son fonctionnement (le reste du travail est effectué grâce aux drivers des périphériques), ce qui, évidemment, est très pratique ;) .

Afin de pouvoir dessiner, nous devons obtenir un handle de contexte de périphérique, traduit en anglais par "Handle on Device Context", ou HDC.

Théorique

Un HDC peut être assimilé, pour une meilleure compréhension, à une surface potentiellement dessinable obtenue par l'appel de GetDC. Cette fonction, prenant comme unique paramètre le handle de la fenêtre dont l'on veut récupérer le DC, retourne un HDC portant sur toute la surface de la fenêtre.

Toutes les fonctions de dessin ne nécessitent généralement qu'un contexte de périphérique. Vous vous demandez alors sûrement comment appliquer des personnalisations temporaires pour chaque dessin ? Eh bien toutes ces personnalisations se passent sur le DC lui-même. La fonction de dessin, utilisant le DC courant, sera lors affectée par les modifications appliquées.

Une fois le DC récupéré, chaque personnalisation possible (changements de couleurs, de fontes...) se fait à l'aide d'une sélection. Cette sélection d'un objet (personnalisation) dans un contexte de périphérique a pour effet de l'appliquer au DC. C'est alors qu'arrive SelectObject. Cette fonction, prenant comme deuxième paramètre l'objet à sélectionner dans le HDC spécifié en premier paramètre, retourne l'ancien objet correspondant avant l'application. Ainsi, pour qu'une couleur seule (par exemple, le vert) soit appliquée sur un HDC, on obtient :

brushDeCouleurAncienne = SelectObject(dcCourant, brushAppliquee);

//Où brushAppliquee = CreateSolidBrush(RGB(0,255,0));

Après utilisation, n'oubliez pas de libérer le DC avec ReleaseDC, qui prend comme unique paramètre le contexte de périphérique à libérer.

Avant de commencer les cas particuliers, un petit cours sur les couleurs (utilisées partout) s'impose :

Les couleurs

Les couleurs sont gérées différemment de la logique actuelle. Je pense en particulier aux couleurs standards comme le RGB, ou Red Green Blue, avec la teneur de chaque couleur primaire variant entre 0 et 255 : en winAPI, les couleurs et motifs sont gérées par Brosses, ou Brushes, et donc HBRUSH.

D'accord, mais comment passer d'une couleur RGB à une Brush ?

Eh bien, Microsoft pense à nous. ;) Nous avons donc à notre disposition CreateSolidBrush prenant comme seul paramètre la valeur hexadécimale de la couleur, ou une variable COLORREF. Pour transformer la couleur RGB en valeur COLORREF afin de la passer en paramètre à CreateSolidBrush, nous pouvons utiliser la macro RGB. Au final, la syntaxe à utiliser afin de récupérer une brush de couleur RGB est la suivante :

const HBRUSH rouge = CreateSolidBrush(RGB(255,0,0));

Les valeurs de chaque couleur primaire se trouvent dans l'ordre des lettres de la macro.

Mais les brosses peuvent aussi être utilisées pour, par exemple, changer de motif. Pour cela, msdn nous propose plusieurs fonctions, parmi lesquelles (à titre d'exemple) CreateHatchBrush, permettant de créer une brosse de motif de pointillés, traits ou autres par le passage du type de motif en premier paramètre, et de sa couleur en deuxième.

Où l'intégrer ?

Tous les dessins peuvent être effectués n'importe où dans le code en récupérant un DC. Seulement, dans le cas d'une superposition d'une autre fenêtre sur celle-ci, tous les dessins se retrouvent effacés. Afin de le contrer, nous devons utiliser un message envoyé par le système quand la fenêtre a besoin d'être redessinée : WM_PAINT.

:( Un scintillement désagréable subsiste...

Exact, la cause en est que les dessins sont alors entièrement redessinés, même s'ils sont toujours visibles. Pour y remédier, il est préférable d'utiliser la fonction BeginPaint à la place de GetDC ; même si elle prend le même paramètre (avec en plus un deuxième : un pointeur sur une structure PAINTSTRUCT pour le stockage... pas importante pour nous) et renvoie aussi un HDC, elle permet de ne redessiner que le strict nécessaire.

N'oubliez pas alors d'utiliser EndPaint, qui prend les mêmes paramètres que BeginPaint et permet d'arrêter le dessin. Elle a en plus le même effet que ReleaseDC.

N'oubliez pas ce que vous venez d'apprendre afin de l'appliquer à ce qui suit.
Rédacteur : Mg++

Le texte

Avant de l'afficher, une personnalisation du texte peut être exécutée (police, couleur).

Personnalisation du texte

Si vous ne savez pas encore ce que signifie le mot fonte, vous devez alors savoir qu'il s'agit d'un modèle d'écriture pour du texte. Pour vous faire une idée, "Times New Roman", "Arial", "Arial Black", etc... sont des fontes (connues qui plus est).
Si vous n'avez toujours pas compris, des fontes sont des polices de caractères :D .

Afin de pouvoir en utiliser lors de fonctions d'affichage de texte, nous devons récupérer un handle de fonte, ou HFONT. La récupération est semblable aux deux précédentes fonctions, et peut être effectuée grâce à CreateFont ou CreateFontIndirect. Comme CreateFont possède un prototype long et détaillé, je préfère présenter l'autre, qui passe indirectement (d'où son nom) par une structure détaillant les spécificités de la police (souligné, gras, italique, police, taille etc...) : LOGFONT.

Avant toute utilisation, certaines structures doivent être remises à zéro : ZeroMemory s'en occupe. Il faut lui passer pour son premier paramètre un pointeur de la structure ciblée, et en deuxième sa taille, donnée par sizeof.

LOGFONT, faisant partie de ce type de structures, doit être initialisée à zéro.
Ensuite, vous pouvez :

  • Choisir la police à utiliser en copiant le nom de la police entre guillemets dans le champ lfFaceName de votre structure LOGFONT. Exemple :

    LOGFONT strctLogfont;

ZeroMemory(&structLogfont, sizeof(LOGFONT));
strcpy(structLogfont, "Arial");

  • Régler la taille de la fonte en définissant le champ lfHeight de la structure (en pixels). Exemple :

    structLogfont.lfHeight = 32;

  • Choisir de souligner ou barrer le texte en réglant les champs lfUnderline ou lfStrikeOut à TRUE. Exemple :

    structLogfont.lfUnderline = TRUE

  • Régler la grosseur du texte en choisissant une valeur pour le champ lfWeight entre 0 et 900 (par pas de 100, il existe des constantes, comme FW_BOLD [gras] à 700). Exemple :

    structLogfont.lfWeight = FW_BOLD;

Plus de précisions sur cette structure seront présentes dans la documentation ci-jointe.

Une fois la structure définie, vous n'avez plus qu'à passer l'adresse de votre structure au seul paramètre possible de CreateFontIndirect, qui s'occupe de vous retourner le handle de fonte que vous devez récupérer. Exemple :

fonte = CreateFontIndirect(&structLogfont);

Affichage de texte

L'affichage du texte à l'écran s'exerce grâce à :

BOOL TextOut (HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cbString);

  • Le premier paramètre désigne le DC courant, dans lequel va être dessiné le texte :

    HDC hdc

  • Le deuxième désigne à quelle position d'abscisse le texte va commencer à être dessiné :

    int nXStart

  • Le troisième a le même effet que précédemment, mais en ordonnée :

    int nYStart

  • Le quatrième désigne la chaîne de caractères à afficher :

    LPCTSTR lpString

  • Le dernier paramètre désigne le nombre de caractères à afficher (généralement le strlen de la chaîne, mais dans le cas où le nombre donné y est inférieur, la chaîne affichée sera tronquée) :

    int cbString

Pour aller plus loin

Vous pouvez :

  • Rendre le fond du texte transparent en utilisant SetBkMode, prenant comme premier caractère le DC du texte, et en deuxième le mode de remplissage du fond (donc ici TRANSPARENT).

  • Appliquer une couleur au texte avec SetTextColor, prenant comme premier paramètre le DC du texte, et comme deuxième sa couleur (sous la forme d'une structure COLORREF).

Code d'exemple

En guise d'exemple, la fonction ci-dessous affiche la chaîne contenue dans chaine, avec les styles spécifiés en arguments, dans la fenêtre présente en argument n°1 :

void dessineTexte (HWND fenetreCiblee, char *chaine, RECT dimensions, BOOL souligne, char *police, COLORREF couleur)
{
        //Déclarations préalables
        LOGFONT structFonte;
        PAINTSTRUCT ps;
        HFONT fonte;
        HDC dc;

        //Remplissage de la LOGFONT
        ZeroMemory(&structFonte, sizeof(LOGFONT));
        strcpy(structFonte.lfFaceName, police);
        structFonte.lfHeight = dimensions.top-dimensions.bottom;
        structFonte.lfWidth = 400;
        structFonte.lfUnderline = souligne;

        //Création de la fonte à partir de structFonte dans fonte
        fonte = CreateFontIndirect(&structFonte);

        //Récupération de la surface de dessin dans dc
        dc = BeginPaint(fenetreCiblee, &ps);

        //Permet au texte d'avoir un fond transparent
        SetBkMode(dc, TRANSPARENT);
        //Applique la fonte au dc de texte
        SelectObject(dc, fonte);
        //Applique la couleur de texte au dc
        SetTextColor(dc, couleur);

        //Affichage du texte
        TextOut(dc, dimensions.left, dimensions.top, chaine, strlen(chaine));

        //Libération de la surface et du dc
        EndPaint(fenetreCiblee, &ps);

        return;
}

Vous avez sûrement noté la présence d'une structure RECT que vous ne connaissez pas.

  • Son champ left désigne l'abscisse du coin supérieur gauche de la surface spécifiée par le RECT.

  • Son champ top désigne son ordonnée.

  • Son champ right désigne l'abscisse du coin inférieur droit de la surface spécifiée par le RECT.

  • Son champ bottom désigne son ordonnée.

Grâce à de petits calculs, je peux alors déterminer la hauteur du texte à afficher. Vous pouvez en inventer un pour la largeur, je ne vous cache pas que j'en ai la flegme :-° .
Rédacteur : Mg++

Les formes géométriques

Afin de créer des formes géométriques, vous avez besoin d'un crayon dans la vie réelle, non ? Eh bien en winAPI, c'est pareil.

Cours

Les crayons

Bon ! Maintenant que vous avez compris le truc (et que vous avez quelques notions de dessin depuis la maternelle :p ), que vous savez donc que le dessin se fait par outils facilement représentables visuellement, je vais vous parler des crayons. Comme vous le savez, un crayon peut être caractérisé par :

  • son motif (la manière dont vous vous en servez)

  • sa taille

  • sa couleur

Ça tombe bien, ce sont exactement les paramètres de CreatePen (et dans l'ordre en plus, quelle chance :-° ). Celle-ci renvoie un handle sur un crayon, ou HPEN.

  • Son motif est spécifié par l'envoi de constantes int au premier de ses paramètres (fnPenStyle), qui peuvent être PS_SOLID (pas de motifs), PS_DASH (tirets), PS_DOTS (pointillés) et j'en passe...

  • Sa taille est spécifiée par le 2ème paramètre, Width (un int) en pixels.

  • Sa couleur est déterminée par le dernier paramètre, crColor, dont le type est encore un COLORREF. La macro RGB est alors encore de vigueur ici.

Les différentes formes

Maintenant que vous avez de quoi dessiner, je vais vous fournir des fonctions de dessin permettant de reproduire, avec le DC courant, une forme géométrique :

  • pour faire un rectangle :

    BOOL Rectangle(HDC hdc,int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);

    (où hdc désigne le dc courant dans lequel va être dessiné le rectangle, et où les 4 paramètres suivants ont la même désignation que dans la structure RECT (cf fin "Le texte").

  • Pour faire une ellipse :

    BOOL Ellipse(HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);

    (Ses paramètres ont la même désignation que pour Rectangle.)

  • Pour faire une ligne, il faut faire une combinaison de :

    BOOL MoveToEx(HDC hdc, int X, int Y, LPPOINT lpPoint);

    Cela permet de déplacer le curseur virtuel dans le dc désigné par le premier paramètre aux coordonnées (X ; Y). Cette fonction écrit les anciennes coordonnées du curseur virtuel dans lpPoint (un pointeur de type POINT est un structure toute bête ne possédant que deux champs : x et y).

    Si NULL est passé à ce paramètre, la fonction ne renvoie pas l'ancienne position.

    BOOL LineTo(HDC hdc, int nXEnd, int nYEnd);

    Ce qui a pour rôle de tracer une ligne (affectée par les objets potentiellement sélectionnés) entre le curseur virtuel et (nXEnd ; nYEnd).

    D'autres fonctions de dessin seront peut être présentées : il s'agit des principales.

Codes d'exemples

Voici un ensemble de fonctions permettant de dessiner un rectangle, une ellipse ou une ligne, avec les options passées en paramètre :

  • Rectangle :

    void dessineRectangle (HWND fenetreCiblee, const RECT dimensions, HPEN crayon, HBRUSH brosse)
{
        //Déclarations préalables + demande de surface de dessin
        PAINTSTRUCT ps;
        HDC dc = BeginPaint(fenetreCiblee, &ps);

        //Dessine le contour avec le crayon donné
        SelectObject(dc, crayon);
        //Peint le fond du rectangle avec la brosse donnée
        SelectObject(dc, brosse);

        //Dessine le rectangle aux positions données
        Rectangle(dc, dimensions.left, dimensions.top, dimensions.right, dimensions.bottom);

        //Libération de la surface et fin du dessin
        EndPaint(fenetreCiblee, &ps);

        return;
}

  • Ellipse :

    void dessineEllipse (HWND fenetreCiblee, const RECT dimensions, HPEN crayon, HBRUSH brosse)
{
        //Déclarations préalables + demande de surface de dessin
        PAINTSTRUCT ps;
        HDC dc = BeginPaint(fenetreCiblee, &ps);

        //Dessine le contour avec le crayon donné
        SelectObject(dc, crayon);
        //Peint le fond du rectangle avec la brosse donnée
        SelectObject(dc, brosse);

        //Dessine l'ellipse aux positions données
        Ellipse(dc, dimensions.left, dimensions.top, dimensions.right, dimensions.bottom);

        //Libération de la surface et fin du dessin
        EndPaint(fenetreCiblee, &ps);

        return;
}

  • Ligne :

    void dessineLigne (HWND fenetreCiblee, const RECT dimensions, HPEN crayon)
{
        //Déclarations préalables + récupération de la surface de dessin
        PAINTSTRUCT ps;
        HDC dc = BeginPaint(fenetreCiblee, &ps);

        //Application du crayon sur le dc
        SelectObject(dc, crayon);

        //Dessin de la ligne
        MoveToEx(dc, dimensions.left, dimensions.top, NULL);
        LineTo(dc, dimensions.right, dimensions.bottom);

        //Libération de la surface + fin du dessin
        EndPaint(fenetreCiblee, &ps);

        return;
}

Rédacteur : Mg++

Les images

Les images affichables en WinAPI sont généralement de type Bitmap. Pour les afficher, vous disposez de deux moyens : par ressources et en passant par un contrôle STATIC, ou par les contextes de périphériques. Voici donc les méthodes disponibles :

Affichage par contrôle STATIC

Cette méthode est la plus simple à mettre en place, et permet de récupérer des notifications à l'égard du contrôle STATIC. Cependant, c'est la plus gourmande en ressources, et n'est donc pas recommandée si vous n'avez aucune utilité des notifications.

Chargement du bitmap

Tout ce que vous avez à faire, c'est intégrer le bitmap en ressources en utilisant cette syntaxe :

img1 BITMAP "Images/bitmap.bmp"
Création du STATIC bitmap

La création de ce type de static s'effectue simplement : contentez-vous de créer un static "normal", de mettre l'ID du bitmap entre guillemets en guise de nom de fenêtre (2ème argument), et d'ajouter le style SS_BITMAP aux autres flag. Les dimensions (6ème et 7ème paramètres) peuvent être mises à 0 pour conserver la taille originelle de l'image.

Si votre contrôle est situé dans un template, la procédure à suivre est la même.

Affichage par contextes de périphériques

C'est la méthode à suivre si vous préférez ne pas utiliser beaucoup les ressources système. Partez pour commencer d'un code simple comme celui-ci :

void afficheBitmap(HWND fenetreCiblee, char *chemin)
{
        HBITMAP bitmap;
        HDC hDC;

        //Chargement du bitmap
        hDC=GetDC(fenetreCiblee);

        //Affichage du bitmap

        DeleteObject(bitmap);
        ReleaseDC(fenetreCiblee,hDC);
}

Si vous avez votre bitmap en ressource, rien de plus simple pour le chargement : LoadBitmap, prenant comme premier paramètre l'instance actuelle, et comme deuxième, son ID entre guillemets, retourne un handle de bitmap directement manipulable. Dans le cas contraire, nous devons utiliser :

HANDLE LoadImage(
    HINSTANCE hinst,
    LPCTSTR lpszName,
    UINT uType,
    int cxDesired,
    int cyDesired,
    UINT fuLoad
);

Dont les arguments sont :

  • l'instance actuelle.

  • Le chemin de l'image.

  • Le type de l'image. Ici IMAGE_BITMAP, mais peut-être aussi IMAGE_ICON ou IMAGE_CURSOR.

  • La largeur voulue de l'image. On utilise ici 0 pour tout prendre.

  • Idem, mais pour la hauteur.

  • Le mode de chargement. L'énumération de toutes ses valeurs possibles étant redondante, je vous précise juste que nous le mettrons à LR_LOADFROMFILE pour le charger à partir du disque.

Donc, dans notre cas ce bout de code ressemblerait alors à ceci :

bitmap = (HBITMAP)LoadImage(NULL,"image.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);

Ensuite, en ce qui concerne l'affichage, vous disposez de deux méthodes possibles, mais la deuxième vous sera présentée plus tard dans une autre de ses possibilités. Voici donc :

DrawState

Elle a comme prototype :

BOOL DrawState(HDC hdc, HBRUSH hbr, DRAWSTATEPROC lpOutputFunc, LPARAM lData, WPARAM wData, int x, int y, int cx, int cy, UINT fuFlags);

Cette fonction permet d'afficher l'image avec certains effets visuels (masques, etc.). Comme ce n'est pas ce que nous voulons faire ici, je passerai sur les paramètres inutiles pour nous, il vous incombera de les mettre à NULL (avec si nécessaire, un cast).

  • hdc désigne le dc courant dans lequel va être affichée l'image.

  • lData désigne le HBITMAP à afficher (un cast en long sera peut être nécessaire).

  • Comme d'habitude, x et y déterminent la position du coin supérieur gauche de l'image, collée dans le dc courant, alors que cx et cy représentent ses dimensions (donc mises à 0 pour conserver la taille originelle).

  • fuFlags désigne le type de l'image à afficher : DST_BITMAP, DST_ICON etc..

Alors, dans notre cas :

DrawState(dc,NULL,NULL,(long)bitmap,NULL,10,10,0,0,DST_BITMAP);

Code complet

Voici donc le code complet. Si vous avez votre image en ressources, inversez les commentaires et le code approprié :

case WM_PAINT :
{
        HBITMAP bitmap;
        HDC dc;
        PAINTSTRUCT ps;

        bitmap=LoadImage(NULL,"image.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);

        dc = BeginPaint(fenetrePrincipale, &ps);

        DrawState(dc,NULL,NULL,(long)bitmap,NULL,10,10,0,0,DST_BITMAP);

        EndPaint(fenetrePrincipale, &ps);

        DeleteObject(bitmap);

        return 0;
}

Vous pouvez en faire une fonction si vous vous sentez d'attaque ;) .

Un approfondissement de ce chapitre avec des techniques avancées vous permettant de faire plus de choses vous sera présenté plus tard dans le cours :) .

Rédacteur : Mg++

Je pense que vous avez maintenant assez de bases pour .... oui, un TP ! :D

Euh ... :waw:

Pas de panique, si vous avez bien compris, rien ne sera difficile. Il s'agira probablement d'un lecteur audio sous Fmod ;)

Les contrôles TP : Un lecteur audio
Grandes lignes
  1. Les bases
  2. Les contrôles
  3. L'affichage
  4. TP : Un lecteur audio
  5. Les événements
  6. Esthétique avancée

L'auteur

pasquestion etpuisquoiencore


Cours sur L'API Winodos de openclassrooms

TP : Un lecteur audio

Voici venu le temps du premier TP. :) Ne vous affolez pas, je vous réserve quelque chose d'assez simple, et toutes les étapes un tantinet plus coriaces vous seront expliquées en guise de pistes.

Temps approximatif..........: 1h - 1h30
Difficulté...........................: Débutant

But

Votre mission, si vous l'acceptez, sera de créer un petit lecteur audio comprenant les fonctions suivantes :

  • Affichage des informations de la musique courrament jouée à l'écran, ainsi que les temps (écoulé/total).

  • Fonctions play / pause / stop.

  • Possibilité de parcourir le disque à la recherche d'une musique (pour le moment une seule, cela sera plus simple pour vous).

  • Présence d'un seeker, en fait un slider, permettant de rechercher un endroit particulier dans la musique (vous le connaissez forcément ;) ).

C'est pour l'instant tout ce que je vous propose, cela pour ne pas vous compliquer la vie en guise de premier TP WinAPI. Des propositions d'améliorations vous seront données en fin de tuto, avec quelques éléments utiles pour leur conception.

Cependant si vous attendez, certaines améliorations en suspens trouveront leur solution dans la suite du cours.

Je vous propose l'organisation suivante, mais libre à vous de faire la vôtre. Ce sera votre lecteur après tout ^^ :

(N'ayez pas peur, je l'ai fait sous Paint :-° .)

Donc, avec cette organisation, le contrôle sur la musique se fera à partir du menu Music (qui possèdera donc les entrées-commandes Play / Pause, et Stop), et le contrôle du player se fera à partir du menu File, qui contiendra donc les entrées-commandes Browse, et Quit.

Maintenant que vous connaissez assez de bases, je me permets de reprendre mon cher anglais. :p

Par conséquent, l'intérieur de la fenêtre ne comportera que les informations générales, et un contrôle de type slider permettant de "seeker" la musique.
Rédacteur : Mg++

Nécessaire

Afin de réaliser le TP, vous avez obligatoirement besoin d'un gestionnaire du son et de la musique. A cet effet, j'ai choisi FMOD, puisqu'il a déjà été étudié dans le tuto de M@teo21.

Si jamais il y a des choses que vous ne comprenez pas (concernant FMOD), ou si vous voulez améliorer le TP, je vous conseille d'utiliser la documentation de FSOUND.

De plus, une bonne connaissance du tuto vous sera nécessaire.

Pourquoi nous avoir appris tant de choses si seuls le slider et les menus sont utilisés, ainsi que l'affichage du texte ?

Ce TP est une occasion de vous entraîner. Ce que je vous propose est le strict minimum à son fonctionnement : si vous voulez l'améliorer (et ce sera sûrement le cas, comme je l'espère ;) ), vous aurez besoin de toutes les connaissances acquises : listbox et dialogbox pour la playlist, boutons, textes et j'en passe...
Rédacteur : Mg++

Pistes

Vous aurez sûrement besoin de quelques pistes afin de le créer... je pense notamment au moyen de parcourir le disque dur à la recherche des musiques ;) . Je vais donc vous les détailler ci-après, afin de vous aider le plus possible.

Voici donc les fameuses pistes (en vrac :-° ) .

Le timer

Afin de changer les informations à chaque seconde, (pour le temps, le slider etc...) vous devrez utiliser un timer. Ceux-ci sont un peu différents de ceux de la SDL : un timer, en WinAPI, envoie à la fenêtre parent de celui-ci une notification toutes les X millisecondes. Très pratiques, ils permettent d'exécuter un bloc d'instructions à chaque intervalle de temps. De plus, ils n'ont pas particulièrement besoin de callback, et sont généralement utilisés sans. Vous devrez donc, à la création de votre fenêtre, initialiser le timer qui, par défaut, n'est pas présent (logique), par un appel à la fonction SetTimer.

  • Son premier argument désigne le handle de la fenêtre parent du timer, qui va recevoir la notification régulière.

  • Son deuxième argument désigne son ID permettant de le distinguer des éventuels autres timer.

  • Son troisième argument désigne l'intervalle de temps, en millisecondes, entre chaque notification.

  • Son dernier argument désigne son éventuelle fonction callback associée, de type TIMERPROC (non nécessaire, et souvent mis à NULL).

Une fois le timer lancé, la fenêtre parent de celui-ci recevra la notification WM_TIMER à chaque intervalle de temps.
Pour arrêter le timer, utilisez KillTimer prenant comme premier argument le handle de la fenêtre parent, et en second l'ID du timer.

L'affichage des textes

L'affichage des textes en utilisant le dessin peut compliquer les choses inutilement. Je vous propose donc d'utiliser une méthode très pratique, qui consiste à créer des contrôles static (avec les positions et dimensions nécessaires bien sûr) pour chaque zone d'information (nom de la musique, son état, et son temps), avec un flag adapté à la zone (pour le temps : SS_RIGHT ; pour le nom : SS_CENTER ; pour le status : SS_LEFT). Et enfin, d'exécuter quand vous voulez changer leur texte SetWindowText. Cette fonction, prenant comme premier paramètre le handle de la fenêtre (donc du static ici), et comme deuxième, le texte à afficher, changera les textes des static en les justifiant à la position que vous avez spécifiée.

Si vous changez la couleur de fond de la fenêtre (vous savez, avec le champ hbrBackground), vous devrez changer le fond des static, et rendre le fond de leurs textes transparents. Pour cela, à la réception de WM_CTLCOLORSTATIC, récupérez le HDC contenu dans le wParam, rendez le texte transparent avec celui-ci, et enfin retournez une brosse (avec CreateSolidbrush) pour colorer le static.

La sélection de musique

En ce qui concerne la fonction Browse (ou parcourir), la manière la plus efficace reste la boîte de dialogue d'ouverture. Vous la connaissez, n'est-ce pas ? C'est ce petit explorateur, vous permettant de sélectionner ce que vous voulez ouvrir :

Pour la faire apparaître, nous utiliserons la fonction GetOpenFileName. Cette dernière, qui prend comme seul paramètre une variable de type OPENFILENAME, retourne TRUE si l'utilisateur a cliqué sur "Open", ou FALSE sur "Cancel". (En gros, vous ne traiterez que le TRUE.) La structure OPENFILENAME n'a pas le même rôle que PAINTSTRUCT : vous devrez la remplir. Pour cela, je vais vous décrire les champs qui nous sont utiles.

Vous devez au préalable la mettre à zéro comme les autres structures remplies.

  • Le champ lStructSize doit récupérer la taille de la structure. (Utilisez donc un sizeof.)

  • Le champ hwndOwner récupère le handle de la fenêtre parent. (Donc celle du player.)

  • Le champ lpstrFile récupère une chaîne de caractères qui va recevoir le chemin de la musique sélectionnée par l'utilisateur, lorsque la fonction retourne.

    Je vous conseille de donner la taille MAX_PATH à votre chaîne de caractères, puisque c'est la taille maximale d'un chemin de fichier.

  • Le champ nMaxFile récupère la taille maximale, en caractères, acceptée par la chaîne de caractères pointée par lpstrFile (en l'occurrence, MAX_PATH).

  • Le champ lpstrFilter est le plus dur à comprendre pour un débutant. Il s'agit des filtres utilisés : ce paramètre désigne les filtres possibles (description affichée à l'écran) avec leur extension filtrée. Pour cela, Microsoft a inventé un format de chaîne de caractères (appelé dans le jargon multistring), composée de plusieurs parties (d'autres chaînes, en fait). Celles-ci sont séparées par des caractères NULL, \0. Les parties fonctionnent par paires : la première partie d'une paire désigne le descriptif affiché à l'utilisateur (il aura le choix entre tous les descriptifs) ; la deuxième, la ou les extensions filtrées (sous la forme *.extension). Dans le cas de plusieurs filtres, ils doivent être séparés par des points-virgules. Comme celui-ci est difficile à comprendre, je vous donne sa valeur : truc.lpstrFilter = "Audio File\0*.mp3;*.wav;*.wma;*.ogg\0\0";

    La dernière partie de string doit posséder deux caractères NULL consécutifs.

  • Le champ nFilterIndex doit toujours être à 1.

  • Le champ Flags désigne les flag à utiliser : ici, nous utiliserons OFN_PATHMUSTEXIST et OFN_FILEMUSTEXIST. Ceux-ci permettent de spécifier que le fichier et son chemin doivent exister (pour éviter les bugs).

Une fois ceci fait, vous devrez passer l'adresse de la variable OPENFILENAME remplie à GetOpenFileName. L'appel de cette fonction suffit à afficher la boîte de dialogue, et à remplir le champ pointé par lpstrFile dès que le bouton "Ouvrir" est cliqué.

Le nom de la musique

Afin de trouver le nom de la musique, je vous propose une solution toute bête, en attendant la gestion des tags : utilisez strrchr sur le chemin de la musique avec le caractère \ (doublé dans le code). Elle retournera alors la fin du chemin, ou la musique elle- même.

Le module de musiques

En ce qui concerne le module de son, fsound, vous aurez besoin de quelques fonctions pour coder votre lecteur. Voici les nécessaires :

  • FSOUND_Init : initialise le module fsound.

  • FSOUND_Close : ferme le module fsound.

  • FSOUND_Stream_Open : ouvre une musique.

  • FSOUND_Stream_Play : joue une musique.

    • Jusqu'ici, vous les connaissez toutes, du tuto de M@teo21. Mais les suivantes n'y sont pas expliquées, vous aurez donc besoin de vous rendre sur la doc de fsound pour des informations les concernant.

  • FSOUND_GetPaused : informe de l'état de la pause.

  • FSOUND_SetPaused : change l'état de la pause.

  • FSOUND_Stream_Stop : stoppe la musique.

  • FSOUND_Stream_GetLengthMs : récupère le temps total de la musique.

  • FSOUND_Stream_SetTime : définit le temps écoulé / actuel de la musique.

  • FSOUND_Stream_GetTime : récupère le temps écoulé/actuel de la musique

Fin des pistes :p

Normalement, avec ces pistes et votre bagage intellectuel acquis durant ce tuto, vous pourriez y arriver sans aide.

Vous devez avoir vu au préalable parmi les contrôles : le static et le seeker. En outre, vous devez connaître toutes les bases, mais je suis sûr que vous les avez déjà. ;)

Allez, pour vous faire une idée du programme final :

Si vous n'y arrivez pas, n'hésitez pas à poster dans le forum du SDZ.

Et maintenant, à vous de jouer ! :pirate:

Bonne chance ! :)
Rédacteur : Mg++

Correction

J'espère que vous vous êtes bien débrouillés. ^^ Dans le cas contraire (ou pour voir comment un autre aurait fait, tout simplement), voici une archive contenant le projet complet (.cbp et codes sources) pour vous faire les dents :magicien: :

Télécharger l'archive du projet !
Si jamais mon site est indisponible ou si le lien est rompu d'une quelconque manière, pour raison d'Etat par exemple, voici une recopie du code source :ninja: :

  • Resource.rc

    /**************************************************
    Nom du fichier : Resource.rc
    Appartenance au projet : TP d'un lecteur audio pour le SDZ
    Créateur : kidpaddle2

    Rôle du fichier : Contient le template du menu
**********************************************/

#include <windows.h>

#include "Constants.h"

ID_MENU MENU
BEGIN
    POPUP "File"
    BEGIN
        MENUITEM "Browse", ID_B_BROWSE
        MENUITEM SEPARATOR
        MENUITEM "Quit", ID_B_QUIT
    END
    POPUP "Music"
    BEGIN
        MENUITEM "Play/Pause", ID_B_PLAYPAUSE
        MENUITEM "Stop", ID_B_STOP
    END
END

  • Constants.h

    /*************************************************************************
    Nom du fichier : Constants.h
    Appartenance au projet : TP d'un lecteur audio pour le SDZ
    Créateur : kidpaddle2

    Rôle du fichier : Définir les constantes nécessaires aux manipulations
                      de contrôles (principalement des ID)
**************************************************************************/

///Protection contre les inclusions multiples
#ifndef CONSTANTES_H
#define CONSTANTES_H

///Définition des constantes
#define ID_B_PLAYPAUSE 0
#define ID_B_STOP 1
#define ID_B_BROWSE 2
#define ID_B_QUIT 3

///Fin de la protection
#endif

  • Functions.h

    /*************************************************************************
    Nom du fichier : Functions.h
    Appartenance au projet : TP d'un lecteur audio pour le SDZ
    Créateur : kidpaddle2

    Rôle du fichier : contient les prototypes des fonctions
**************************************************************************/

///Protection contre les inclusions multiples
#ifndef FUNCTIONS_H
#define FUNCTIONS_H

///Includes nécessaires
#include <windows.h>
#include <commctrl.h>
#include "Music.h"

///Prototypes des fonctions
void loadMusic(HWND, Music*);
void createStatics (HWND[], HWND, HINSTANCE);
void adaptSeeker (HWND, const Music);

///Fin de la protection
#endif

  • Functions.cpp

    /*************************************************************************
    Nom du fichier : Functions.cpp
    Appartenance au projet : TP d'un lecteur audio pour le SDZ
    Créateur : kidpaddle2

    Rôle du fichier : définir les fonctions nécessaires au fonctionnement
                      du player
**************************************************************************/

///Includes
#include "Functions.h"

/** Fonction : loadMusic
    Paramètre 1 : Fenêtre parent du contrôle
    Paramètre 2 : adresse de l'objet "Music" pour charger la musique
    Rôle : Charger la musique grâce à une boîte de dialogue d'ouverture,
           inscrire le chemin et nom, et déterminer le temps total de la
           musique**/
void loadMusic (HWND parentWindow, Music *musicToLoad)
{
    //Structure nécessaire à la boîte de dialogue d'ouverture
    OPENFILENAME toGetFileName;
    //Variable qui va recevoir le chemin de la musique
    char filePath[MAX_PATH] = "";

    //Remplissage de la structure
    ZeroMemory(&toGetFileName, sizeof(OPENFILENAME));
    toGetFileName.lStructSize = sizeof(OPENFILENAME);
    toGetFileName.hwndOwner = parentWindow;
    toGetFileName.lpstrFile = filePath;
    toGetFileName.nMaxFile = MAX_PATH;
    //Avec un filtre de fichiers audio
    toGetFileName.lpstrFilter = "Audio File\0*.mp3;*.wav;*.ogg;*.wma\0\0";
    toGetFileName.nFilterIndex = 1;
    toGetFileName.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

    //Si l'utilisateur clique sur "Ouvrir"
    if(GetOpenFileName(&toGetFileName) == TRUE)
    {
        //Copier le chemin récupéré dans le chemin de la musique
        strcpy(musicToLoad->path, filePath);
        //Inscrire le nom de la musique
        strcpy(musicToLoad->name, strrchr(musicToLoad->path, '\\'));
        //Si la musique est changée, on ferme la précédente
        FSOUND_Stream_Close(musicToLoad->stream);
        //Ouverture de la musique
        musicToLoad->stream = FSOUND_Stream_Open(musicToLoad->path, 0, 0, 0);
        //Récupération du temps total
        musicToLoad->totalTime = FSOUND_Stream_GetLengthMs(musicToLoad->stream)/1000;
    }

    return;
}

/** Fonction : createStatics
    Paramètre 1 : tableau de statics à créer
    Paramètre 2 : fenêtre parent
    Paramètre 3 : Instance actuelle
    Rôle : Créer les statics montrant les infos sur la musique**/
void createStatics (HWND statics[], HWND parentWindow, HINSTANCE instance)
{
    //Création du premier static (nom de la musique)
    statics[0] = CreateWindow("STATIC",
                            "No musics loaded.",
                            WS_CHILD | WS_VISIBLE | SS_CENTER | SS_NOPREFIX,
                            10, 11, 370, 15,
                            parentWindow,
                            (HMENU)NULL,
                            instance,
                            NULL);

    //Création du deuxième static (status de la musique)
    statics[1] = CreateWindow("STATIC",
                            "Currently idle.",
                            WS_CHILD | WS_VISIBLE | SS_LEFT | SS_NOPREFIX,
                            10, 55, 100, 15,
                            parentWindow,
                            (HMENU)NULL,
                            instance,
                            NULL);

    //Création du troisième static (temps de la musique)
    statics[2] = CreateWindow("STATIC",
                            "",
                            WS_CHILD | WS_VISIBLE | SS_RIGHT | SS_NOPREFIX,
                            240, 55, 140, 15,
                            parentWindow,
                            (HMENU)NULL,
                            instance,
                            NULL);

    return;
}

/** Fonction : adaptSeeker
    Paramètre 1 : seeker
    Paramètre 2 : objet musique chargé
    Rôle : Adapter les marges du seeker en fonction du temps total de la
           musique **/
void adaptSeeker (HWND seeker, const Music music)
{
    //Définit la marge supérieure du seeker au temps total de la musique
    SendMessage(seeker, TBM_SETRANGE, FALSE, MAKELONG(0,music.totalTime));

    return;
}

  • Music.h

    #ifndef MUSIC_H
#define MUSIC_H

#include <stdio.h>
#include <fmod/fmod.h>

typedef enum Status
{
    playing = 0, paused = 1, stopped = 2, idle = 3
}Status;

typedef struct st1
{
    int elapsedTime;
    int totalTime;
    char allTimes[20];

    char name[256];
    char path[MAX_PATH];

    FSOUND_STREAM *stream;
}Music;

#endif

  • main.cpp

    /*************************************************************************
    Nom du fichier : main.cpp
    Appartenance au projet : TP d'un lecteur audio pour le SDZ
    Créateur : kidpaddle2

    Rôle du fichier : point d'entrée du programme et gestion des évènements
                      (callbacks)
**************************************************************************/

///Includes
#include <windows.h>
#include <fmod/fmod.h>

#include "Constants.h"
#include "Music.h"
#include "Functions.h"

///Instance actuelle (on en a besoin partout, donc on la déclare globale).
HINSTANCE instance;

///Prototype de la procédure callback du player
LRESULT CALLBACK playerProc(HWND, UINT, WPARAM, LPARAM);

///Winmain
int WinMain (HINSTANCE hThisInstance, HINSTANCE hPreviousInstance,
LPSTR lpCmdLine, int nFursterStill)
{
    HWND player = NULL;
    MSG message;
    WNDCLASS playerClass;

    //Remplissage de l'instance globale avec l'instance actuelle
    instance = hThisInstance;

    //Définition de la classe de fenêtre
    playerClass.style = 0;
    playerClass.lpfnWndProc = playerProc;
    playerClass.cbClsExtra = 0;
    playerClass.cbWndExtra = 0;
    playerClass.hInstance = instance;
    playerClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    playerClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    //Remarquez la brosse employée pour peindre le fond de la fenêtre
    playerClass.hbrBackground = CreateSolidBrush(RGB(0,0,0));
    //Le menu est associé
    playerClass.lpszMenuName = "ID_MENU";
    playerClass.lpszClassName = "pClass";

    if(!RegisterClass(&playerClass))
        return FALSE;

    //Création de la fenêtre du player
    player = CreateWindow("pClass", "Basic Music Player", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 130,
                                                   NULL, NULL, hThisInstance, NULL);
    if (!player) return FALSE;

    //On l'affiche
    ShowWindow(player, nFursterStill);
    UpdateWindow(player);

    //Boucle évènementielle
    while (GetMessage(&message, NULL, 0, 0))
    {
        TranslateMessage(&message);
        DispatchMessage(&message);
    }
    return message.wParam;
}

///Procédure callback de la fenêtre du player
LRESULT CALLBACK playerProc(HWND playerWindow, UINT message, WPARAM wParam, LPARAM lParam)
{
    /*Variables déclarées statiques pour qu'elles soient toujours valides
    tout au long du programme*/
    static Music music = {0};
    //Handles des 3 statics (pour les infos) et du seeker
    static HWND infos[3] = {NULL}, seeker = NULL;
    //Status
    static Status status = idle;

    switch (message)
    {
        case WM_CREATE:
            //Création de tous les contrôles
            createStatics(infos, playerWindow, instance);

            seeker = CreateWindow((LPCTSTR)TRACKBAR_CLASS,
                      (LPCTSTR)"",
                      (DWORD)WS_CHILD|WS_VISIBLE | TBS_NOTICKS,
                      10, 30, 370, 20,
                      playerWindow,
                      (HMENU)NULL,
                      instance,
                      NULL);

            //Initialisation de fsound
            FSOUND_Init(44100, 32, 0);

            //Lancement du timer nécessaire
            SetTimer(playerWindow, 0, 1000, (TIMERPROC)NULL);
            return 0;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                /*Si la commande "Quit" du menu est cliquée, on envoie WM_DESTROY à la fenêtre*/
                case ID_B_QUIT:
                    SendMessage(playerWindow, WM_DESTROY, 0, 0);
                    break;

                /*Si la commande "Browse" du menu est cliquée, on s'occupe de charger la musique*/
                case ID_B_BROWSE:
                    //Donc on charge la musique (voir Functions.cpp)
                    loadMusic(playerWindow, &music);
                    /*On adapte les marges du seeker en conséquence (du temps total)*/
                    adaptSeeker(seeker, music);
                    //On met à jour le nom de la musique
                    SetWindowText(infos[0], music.name);
                    break;

                //Si la commande "Play / Pause" du menu est cliquée et :...
                case ID_B_PLAYPAUSE:
                    /*...si aucune musique n'est jouée, et que la pause n'est pas enclenchée*/
                    if(!FSOUND_IsPlaying(0) && !FSOUND_GetPaused(0))
                        //Alors on joue la musique chargée
                        FSOUND_Stream_Play(0, music.stream);
                    //...sinon si la pause est enclenchée
                    else if(FSOUND_GetPaused(0))
                        //On l'enlève
                        FSOUND_SetPaused(0, FALSE);
                    //...sinon si on est en pleine lecture
                    else if(FSOUND_IsPlaying(0))
                        //Alors on enclenche la pause
                        FSOUND_SetPaused(0, TRUE);
                    //...sinon on fait rien.
                    else
                        break;
                    break;

                //Si la commande "Stop" du menu est cliquée...
                case ID_B_STOP:
                    //...on stoppe la musique chargée
                    FSOUND_Stream_Stop(music.stream);
                    break;
            }
            return 0;

        //Si le seeker est bougé...
        case WM_HSCROLL:
            //On trouve la position changée du seeker...
            music.elapsedTime = SendMessage(seeker, TBM_GETPOS, 0, 0);
            //... et on applique à la musique le temps demandé
            FSOUND_Stream_SetTime(music.stream, music.elapsedTime*1000);
            break;

        //A chaque signal de timer...
        case WM_TIMER:
            //Et si la musique est jouée
            if(FSOUND_IsPlaying(0))
            {
                /*Et si la pause est enclenchée, on rafraîchît le status en conséquence*/
                if(FSOUND_GetPaused(0))
                {
                    /* Seulement si ça ne l'est pas déjà */
                    if(status != paused)
                    {
                        SetWindowText(infos[1], "Paused.");

                        //Le status est changé
                        status = paused;
                    }
                }
                //Si la pause ne l'est pas
                else
                {
                    //On rafraîchit aussi le status en conséquence
                    /* Seulement si ça ne l'est pas déjà */
                    if(status != playing)
                    {
                        SetWindowText(infos[1], "Playing...");

                        //Le status est changé
                        status = playing;
                    }

                    //On récupère le temps actuel de la musique
                    music.elapsedTime = FSOUND_Stream_GetTime(music.stream)/1000;

                    /*On fabrique une chaine de caractères montrant l'avancée du temps*/
                    sprintf(music.allTimes,
                            "%d\' %d\" / %d\' %d\"",
                            music.elapsedTime/60,
                            music.elapsedTime%60,
                            music.totalTime/60,
                            music.totalTime%60);

                    //... et on l'affiche dans l'emplacement prévu
                    SetWindowText(infos[2], music.allTimes);
                    //Et enfin on met à jour le seeker
                    SendMessage(seeker, TBM_SETPOS, TRUE, music.elapsedTime);
                }
            }
            //Si la musique n'est pas en train d'être jouée
            else
            {
                /*On rafraîchit le status et l'avancée du temps en conséquence*/
                /* Seulement si ça ne l'est pas déjà */
                if((status != stopped) && (status != idle))
                {
                    SetWindowText(infos[1], "Stopped.");
                    SetWindowText(infos[2], "");

                    //Et on met à zéro le seeker
                    SendMessage(seeker,TBM_SETPOS, TRUE, 0);

                    //Le status change
                    status = stopped;
                }
            }
            return 0;

        //Si le sontrôle demande à être recoloré...
        case WM_CTLCOLORSTATIC:
        {
            //On récupère un DC dessus...
            HDC textDC = (HDC)wParam;
            SetTextColor(textDC, RGB(0,255,0));
            //Qui permet de nous donner la main sur le texte à rendre transparent
            SetBkMode(textDC, TRANSPARENT);

            static HBRUSH brush = CreateSolidBrush(RGB(0,0,0)); /* Il vaut mieux la mettre en static pour éviter des bugs graphiques au chargement */

            /*Et finalement on retourne une brosse pour colorer son fond. (la même que la fenêtre principale)*/
            return (LRESULT)brush;
        }

        //Si on demande à quitter
        case WM_DESTROY:
            //On ferme fsound
            FSOUND_Close();
            //et on quitte...fini.
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(playerWindow, message, wParam, lParam);
    }
}

Rédacteur : Mg++

Améliorations

Ce lecteur est un bon début, mais il n'est pas parfait (nooon sans blague ? :p ). Je vous propose ici quelques idées d'améliorations avec, si besoin est, quelques astuces pour les concrétiser.

Sélection multiple

Vous pourriez permettre au lecteur de récupérer plusieurs musiques au lieu d'une dans la boîte de dialogue d'ouverture. Comme vous avez pu le voir, elle ne le permet pas. Pour cela, ajoutez à la liste de flags OFN_MULTISELECT, qui permettra de faire une sélection multiple. Mais... je ne vous en ai pas parlé car c'est un peu plus dur que pour la sélection unique. ;) En effet, vous ne disposez que d'une chaîne de sortie (lpszFile). Pas de panique, Microsoft est là. Dans le cas d'une sélection multiple, la chaîne se transforme alors en une multistring, dont je vous ai parlé plus haut. Le chemin du dossier où se trouvent les musiques, ainsi que chaque nom de musique sélectionnée qu'il précède, sont alors séparés par un caractère NULL, et le dernier caractère est doublé. Il va donc falloir que vous inventiez une fonction (je ne vais pas la donner, sinon ce n'est pas drôle :lol: ) permettant de découper la chaîne en un tableau de string, que vous stockerez alors. Voici alors son format, en guise de coup de pouce ^^ :

"C:\\Dossier1\\Dossier2\\Musique1.mp3\0Musique2.wav\0Musique3.ogg\0\0"

Gestion de plusieurs musiques

Après l'avoir intégré, un système de progression entre les musiques serait intéressant : l'ajout de 'Next', 'Previous' serait alors adapté, et un choix entre lecture normale ou aléatoire pourrait être ajouté.

Ajout d'une playlist

Une playlist pourrait alors être ajoutée, pour représenter visuellement les musiques chargées. Un double-clic sur une entrée de celle-ci lançerait alors la musique.

Fonctions habituelles

Après, pourraient être insérées les fonctions normales, tel que réglage du volume, etc.

Possibilités autres

D'autres possibilités seront vues plus en détail lors de la progression du cours, comme un skin élaboré dans une fenêtre non rectangulaire en utilisant les régions, la possibilité de minimiser l'application, etc.

Déjà, si vous intégrez toutes ces idées, cela prouvera que vous vous débrouillez bien avec le winAPI. Quoiqu'il arrive, gardez une organisation propre, et les ajouts se feront sans problème. :ange:
Rédacteur : Mg++

Et voilà, ce premier TP est fini :) Je vous retrouve après dans la suite du cours, et j'espère que ce TP vous a fait du bien. ;)
Rédacteur : kidpaddle2

L'affichage Les événements
Grandes lignes
  1. Les bases
  2. Les contrôles
  3. L'affichage
  4. TP : Un lecteur audio
  5. Les événements
  6. Esthétique avancée

L'auteur

pasquestion etpuisquoiencore


Cours sur L'API Winodos de openclassrooms

Les événements

Vous avez jusqu'ici exclusivement travaillé avec des contrôles pour intéragir avec la fenêtre. Je vous propose ici de traiter manuellement les principaux événements du clavier et de la souris.

Ces événements sont captés sous forme de messages (notifications) par la fenêtre concernée, si elle a le focus.

Evénements "clavier"

Il y a trois événements vraiment utilisés :

  • WM_KEYDOWN

  • WM_CHAR

  • WM_KEYUP

Chacun de ces messages survient l'un après l'autre (dans l'ordre), à chaque fois qu'une touche est utilisée.

Si la touche enfoncée n'est pas un caractère, WM_CHAR n'est pas envoyé.

Voici un schéma résumant le tout :

Schéma des messages générés tout au long de l'utilisation d'une touche
Comme vous pouvez le voir, WM_KEYDOWN survient lors de l'enfoncement d'une touche, WM_CHAR si cette touche désigne un caractère, et WM_KEYUP lors du relâchement de la touche.

Tous ces messages prennent comme paramètre :

  • WPARAM : code virtuel de la touche

  • LPARAM : informations de répétition, d'état de touche système etc.

Le paramètre LPARAM n'est souvent pas utilisé. Par contre, le premier permet de déterminer la touche qui a généré l'événement, à travers une correspondance code/touche, dont le tableau est disponible à l'adresse suivante (je n'ai pas eu le courage de tout retranscrire dans un tableau zCode, mais si une âme charitable pouvait le faire, je lui en serait reconnaissant ^^ ).

Pour les caractères (A->Z :p ), leur code virtuel n'est autre que leur valeur. Par exemple, le code virtuel de la touche a n'est autre que 'a' (ou 97 en ASCII), ou 'A' (ou 65 en ASCII), si la touche est majuscule. Naturellement, vous connaissez tout ça ;)

Il n'y a pas grand-chose à dire d'autre, donc un code complet devrait suffire à vous mettre en tête leur utilisation.

Les codeurs C++ pourraient alors avoir l'occasion d'utiliser le conteneur standard "map" pour la correspondance entre le nom de la touche, et son code virtuel.

Pour ceux que ça intéresse, vous pouvez récupérer l'état actuel d'une touche du clavier en utilisant GetKeyState(), prenant comme paramètre le code virtuel de la touche visée (attention, pour les caractères le paramètre passé doit être leur valeur ASCII). Son retour est (j'ai simplifié) inférieur à 0 si la touche est enfoncée.

Exemple

Voici donc un exemple de traitement des trois messages (non compilable), qui permet simplement de quitter le programme à l'appui de la touche "Echap", et d'ajouter à une string les éventuels caractères entrés.

//Soit wParam et lParam les WPARAM et LPARAM (normal ^^ )
//Soit buffer une instance de std::string

/* switch du MSG dans la callback de la fenêtre */
case WM_KEYDOWN:
//ou WM_KEYUP
    switch(wParam)
    {
        case VK_ESCAPE:
            PostQuitMessage(0);
            break;

        default:
            break;
    }
    return 0;

case WM_CHAR:
    buffer += (char)wParam;
    return 0;

C'est tout bête, n'est-ce pas ?
Rédacteur : Mg++

Evénements "souris"

Les boutons

La souris génère deux messages consécutifs à l'appui d'un de ses bouton, chacun décliné en trois messages, selon le bouton concerné. La nomenclature de tels message est alors WM_BOUTONMESSAGE où :

  • BOUTON est l'identifiant du bouton ayant généré le message pouvant être :

    • L (bouton gauche)

    • R (bouton droit)

    • M (bouton du milieu)

  • MESSAGE le type d'événement, pouvant être :

    • BUTTONDOWN

    • BUTTONUP

On retrouve ici les suffixes -DOWN et -UP, montrant que durant l'utilisation d'un bouton, WM_(L, R ou M)BUTTONDOWN est envoyé quand il est enfoncé, et WM_(L, R ou M)BUTTONUP quand il est relâché.

Ces messages ont tous les mêmes paramètres WPARAM et LPARAM : le paramètre WPARAM contient un masque de bouton, c'est-à-dire le code virtuel de touches/boutons étant enfoncés lorsque l'événement survient (équivalent à la traduction d'appuis simultanés), dont les valeurs peuvent être :

  • MK_CONTROL : la touche Ctrl est enfoncée

  • MK_LBUTTON : le bouton gauche est enfoncé

  • MK_RBUTTON : Le bouton droit est enfoncé

  • MK_SHIFT : la touche SHIFT est enfoncée

Ces flags pouvant être combinés, vous pouvez retrouver la présence de l'un ou l'autre en utilisant l'opérateur &. Pour plus d'informations, reportez-vous au tutorial sur les flags.

Par ailleurs, le paramètre LPARAM contient les coordonnées du curseur lors de l'émission du message : le LOWORD contient son abscisse, et le HIWORD son ordonnée. Ces coordonnées peuvent êtres récupérées à l'aide des macros GET_X_LPARAM(LPARAM) (pour l'abscisse) et GET_Y_LPARAM(LPARAM) (pour l'ordonnée), ou encore MAKEPOINTS(LPARAM) (pour récupérer une structure POINTS).

Un autre message similaire (de même nomenclature et paramètres) existe, envoyé lors d'un double clic de la souris : WM_(L, R ou M)DBLCLK. Attention, pour pouvoir le recevoir votre fenêtre doit comporter le style CS_DBLCLKS.

La molette

L'événement WM_MOUSEWHEEL, généré lors de l'utilisation de la molette, possède les mêmes paramètres, excepté le fait que le masque de bouton expliqué plus haut est contenu par le HIWORD du WPARAM.

Pourquoi ?

J'y viens :p : le LOWORD est en fait utilisé par l'angle parcouru par la molette, défini à partir de la constante WHEEL_DELTA (valant 120, et correspondant en général à un "cran" de la molette). Si ce paramètre est positif, c'est que la molette a été tournée vers le "haut", ou à l'opposé de l'utilisateur. A l'inverse, une valeur négative traduit un mouvement vers le "bas", ou vers l'utilisateur.

Le déplacement

Nous avons vu jusqu'ici les événements des boutons, et de la molette de la souris. Mais qu'en est-il de son mouvement ?

Quand la souris est déplacée, le message WM_MOUSEMOVE est envoyée à la fenêtre située sous le curseur. Celui-ci (chance ! :p ) possède exactement les mêmes paramètres que les messages correspondant à l'utilisation des boutons de la souris, à savoir les boutons pressés simultanément, et les coordonnées du curseur.

Cet événement survenant à des intervalles très rapprochés, je ne vous conseille pas de le traiter avec de lourdes opérations, ou votre processeur en souffrira ;)

Divers

Vous pouvez récupérer la position de la souris à n'importe quel endroit de votre code, avec la fonction GetCursorPos(), prenant comme paramètre un pointeur de POINT. Vous l'aurez deviné, la fonction SetCursorPos() permettrait de définir la position du curseur, en prenant un POINT en paramètre.

Exemple

Voici un code récapitulatif tout simple, déduisant simplement la distance parcourue par le curseur (quand le bouton gauche de la souris est enfoncé), et par la molette (non compilable, comme d'habitude ;) ).

#include <math.h>

#define RADIUS (2*PI)

double linearDistance(POINT start, POINT end)
{
    return sqrt(pow((end.x - start.x), 2) + pow((end.y - start.y), 2))
}

int DEG_TO_RAD(int deg)
{
    return (deg*PI)/180
}

double angularDistance(int angle)
{
    return abs(angle)*RADIUS;
}

/* Soit cursorDistance et wheelDistance des static double, représentant respectivement
   la distance parcourue par le curseur et la molette (en pixels), ainsi que
   BOOL lBtnDown l'état du bouton gauche de la souris. (TRUE pour
   enfoncé, FALSE pour relâché) (FALSE par défaut) */

//WindowProc, switch MSG (avec LPARAM lParam et WPARAM wParam)

case WM_LBUTTONDOWN:
    lBtnDown = TRUE;
    return 0;

case WM_LBUTTONUP:
    lBtnDown = FALSE;
    return 0;

case WM_MOUSEMOVE:
{
    if(lBtnDown)
    {
        static POINT old = {0,0};
        POINT current;
        current.x = GET_X_LPARAM(lParam);
        current.y = GET_Y_LPARAM(lParam);

        cursorDistance += linearDistance(old, current);

        old = current;
    }

    return 0;
}

case WM_MOUSEWHEEL:
    wheelDistance += angularDistance(DEG_TO_RAD(LOWORD(lParam)));
    return 0;

DEG_TO_RAD devrait être précisé inline en C++, je n'aime pas utiliser des macros...

Rédacteur : Mg++

Ce fut une courte introduction aux événements principaux du clavier et de la souris. Un prochain chapitre sera dédié en partie aux événements système, comme le Drag And Drop.

Si vous comptez envoyer ces messages à une fenêtre, préférez PostMessage() à SendMessage(), puisqu'en général on n'attend pas de réponse de la part de la fenêtre visée (elle possède les mêmes paramètres que cette dernière).

Certains des messages necessitent des LPARAM/WPARAM contenant un LOWORD et un HIWORD. Dans ce cas, vous pouvez en créer en utilisant les macros MAKELPARAM(WORD, WORD) et MAKEWPARAM(WORD, WORD).

TP : Un lecteur audio Esthétique avancée
Grandes lignes
  1. Les bases
  2. Les contrôles
  3. L'affichage
  4. TP : Un lecteur audio
  5. Les événements
  6. Esthétique avancée

L'auteur

pasquestion etpuisquoiencore


Cours sur L'API Winodos de openclassrooms

Esthétique avancée

Je suis certain que vous avez déjà éprouvé le désir de concevoir des applications ayant un design plus évolué à base de skins, formes fantaisistes etc., comme le proposent la plupart des logiciels sur la toile.

Eh bien, je vous propose ici de le réaliser ;) . Vous verrez donc ici comment tailler votre fenêtre comme bon vous semble. Pour cela, il est bien entendu necessaire que vous ayiez lu auparavant les autres chapitres de ce tutorial, et en particulier L'affichage (pour les images), et Les événements (surtout pour les tests relatifs à la souris, les boutons étant pour le moment incrustés au skin*).

Comme vous le savez tous à ce stade, l'API Windows est particulièrement tordue, ou tout du moins un tantinet compliquée lorsqu'il s'agit de dessin. Par conséquent, je ne vous exposerai pas ici les moyens de faire des boutons hovers (changeant d'état selon le stade de la souris). Vous le verrez plus tard, durant un chapitre du même thème, mais plus avancé.

Commençons tout d'abord par tailler fenêtre, non pas avec un burin, mais avec les régions.

Les régions

En premier lieu, il est nécessaire de définir ce qu'est une région. En fait, vous pouvez vous la modéliser comme un moule. Je ne sais pas si l'image vous interpelle, mais c'est comme les formes métalliques que vous appliquez sur un gâteau pour qu'il en prenne justement la forme ^^ .

L'usage de ces régions se définit en plusieurs étapes :

  • Création d'une région

  • Application de la région à une fenêtre.

Procédons dans l'ordre (tant qu'à faire :p ), et commençons dont par la création des régions proprement dite.

Important : veillez à donnner le style WS_POPUP à la fenêtre sur laquelle va être appliquée la région crée, car dans le cas contraire elle ne le sera pas (WS_OVERLAPPEDWINDOW est, rappelons-le, incompatible avec WS_POPUP. Vous devrez donc le remplacer par celui-ci).

Création d'une région

A l'instar des contrôles, une région possède un handle permettant de la manier. Celui-ci, dont le type est HRGN, contient les informations relatives à la région visée. Cette structure est remplie à partir de fonctions de création de région, autorisant un large éventail de formes possibles, à travers différentes fonctions.

Voyez-plutôt :

  • CreateRectRgn()

  • CreateRoundRectRgn()

  • CreateEllipticRgn()

  • CreatePolygonRgn()

  • ...

Celles ci-dessus étant les principales, nous ne verrons que celles-ci (la liste complète se trouvant bien entendu sur msdn).

CreateRectRgn()

CreateRectRgn() permet de créer une région de forme rectangulaire, dont les coordonnées des coins supérieur gauche et inférieur droit sont données.

Le prototype de CreateRectRgn() est le suivant :

HRGN CreateRectRgn(
  int nLeftRect,   //Abscisse du coin supérieur gauche de la région
  int nTopRect,    //Ordonnée du coin supérieur gauche de la région
  int nRightRect,  //Abscisse du coin inférieur droit de la région
  int nBottomRect  //Ordonnée du coin inférieur droit de la région
);

Variante : vous pouvez utiliser CreateRectRgnIndirect() à partir d'une simple structure (CONST) RECT*.

CreateRoundRectRgn()

CreateRoundRectRgn() permet de créer une région de la forme d'un rectangle ayant des coins arrondis, et dont les coordonnées des coins supérieur gauche et inférieur droit, ainsi que les dimensions de l'éllipse (permettant d'obtenir ces arrondis) sont données.

Le prototype de CreateRoundRectRgn() est le suivant :

HRGN CreateRoundRectRgn(
  int nLeftRect,      //Abscisse du coin supérieur gauche de la région
  int nTopRect,       //Ordonnée du coin supérieur gauche de la région
  int nRightRect,     //Abscisse du coin inférieur droit de la région
  int nBottomRect     //Ordonnée du coin inférieur droit de la région
  int nWidthEllipse,  //Largeur de l'ellipse inscrite
  int nHeightEllipse  //Hauteur de l'ellipse inscrite
);
CreateEllipticRgn()

CreateEllipticRgn() permet de créer une région de forme élliptique, dont les coordonnées des coins supérieur gauche et inférieur droit du rectangle dans lequel elle est inscrite sont données.

Le prototype de CreateEllipticRgn() est le suivant :

HRGN CreateEllipticRgn(
  int nLeftRect,   //Abscisse du coin supérieur gauche du rectangle circonscrit
  int nTopRect,    //Ordonnée du coin supérieur gauche du rectangle circonscrit
  int nRightRect,  //Abscisse du coin inférieur droit du rectangle circonscrit
  int nBottomRect  //Ordonnée du coin inférieur droit du rectangle circonscrit
);

Variante : vous pouvez utiliser CreateEllipticRgnIndirect() à partir d'une simple structure (CONST) RECT*.

CreatePolygonRgn()

CreatePolygonRgn() n'est certes pas la fonction la plus pratique de celles exposées, mais reste la plus intéressante. En effet, en procurant à la fonction un tableau de coordonnées de points (sous la forme d'un tableau de POINT (CONST) POINT*), leur nombre ainsi que le mode de remplissage (voir ci-après), vous pouvez créer exactement la région que vous souhaitez.

Son prototype est le suivant :

HRGN CreatePolygonRgn(
  CONST POINT *lppt,  //Tableau de points
  int cPoints,        //Nombre de points dans le tableau
  int fnPolyFillMode  //Mode de remplissage du polygone
);

Une petite mise au point s'impose quant au mode de remplissage. Celui-ci peut prendre les valeurs ALTERNATE, et WINDING. ALTERNATE est souvent conseillé, et décrit le procédé suivant : selon l'algorithme scanline, la zone comprise entre les arêtes impaires et paires du polygone correspondra à la région créée. (pour plus amples informations, cherchez sur la Toile :-° )

Opérations diverses

Parfois, ces fonctions ne suffisent pas pour obtenir les formes dont vous avez envie (par exemple quand vous n'avez pas envie de récupérer des points pour créer votre région >_ ). Dans ces cas là, on les complète par d'autres fonctions, comme CombineRgn(), pour étendre encore plus les possibilités.

Celle-ci permet de combiner deux régions, selon le mode de combinaison dont les valeurs possibles sont :

  • RGN_AND : La région obtenue est l'intersection des deux régions données

  • RGN_DIFF : La région obtenue est la combinaison des parties de la première des deux régions données, qui n'intersectent pas la deuxième.

  • RGN_OR : La région obtenue est l'union des deux régions données.

  • RGN_XOR : [Comme RGN_OR, sans les intersections des deux régions.]

Voici donc son prototype :

int CombineRgn(
  HRGN hrgnDest,      //Handle de la région de destination
  HRGN hrgnSrc1,      //Handle de la première des deux régions à combiner
  HRGN hrgnSrc2,      //Handle de la deuxième des deux régions à combiner
  int fnCombineMode   //Mode de combinaison des deux régions
);

La fonction peut alors retourner :

  • NULLREGION : La région obtenue est vide

  • SIMPLEREGION : La région obtenue est un rectangle

  • COMPLEXREGION : La région obtenue est plus qu'un simple rectangle

  • ERROR : Aucune région ne fut générée

Le but de ce tuto, c'est un peu d'avoir une fenêtre de la forme de son skin, non ? Je vais mettre des années à créer ma région !

Pas de panique, ces fonctions ne sont que des bases (pouvant néanmoins vous servir ultérieurement) à une fonction parfaitement adaptée : BmpToRgn() :ange:

Cette fonction (que j'ai découverte sur cppfrance.com à mes débuts, et créée par anthraxx) permet de récupérer une région à partir d'une image bitmap (donc d'une image 32bits) et d'une couleur de transparence (par défaut, noir).

Voici le code complet de la fonction :magicien: :

HRGN BmpToRgn (HBITMAP hBmp, COLORREF cTransparentColor)
{
 #define ALLOC_UNIT 100

    int y, x;
    HRGN hRgn = NULL;

    if (!hBmp) return 0; // si bitmap invalide retourne

    BITMAP bm;
    GetObject(hBmp, sizeof(bm), &bm); // met les infos d'en tete du bitmap dans bm
    UINT siz=bm.bmWidth*bm.bmHeight*4; // enregistre la taille des donnes de l'image

    char *lpBmpBits=(char*)LocalAlloc(LMEM_FIXED,siz); // fait de la place pour les bits du bitmap
    GetBitmapBits(hBmp,siz,lpBmpBits); // obtient les bits de l'image dans l'espace qu'on a reservé
    bm.bmBits=lpBmpBits; // complete la strucutre bm avec les bits

    while (bm.bmWidthBytes % 4) bm.bmWidthBytes++; // bmWidthBytes doit être divisible par 4

    DWORD maxRects = ALLOC_UNIT;
    HANDLE hData = GlobalAlloc(GMEM_MOVEABLE, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects));
    RGNDATA *pData = (RGNDATA *)GlobalLock(hData);
    pData->rdh.dwSize = sizeof(RGNDATAHEADER);
    pData->rdh.iType = RDH_RECTANGLES;
    pData->rdh.nCount = pData->rdh.nRgnSize = 0;
    SetRect(&pData->rdh.rcBound, MAXLONG, MAXLONG, 0, 0);

    BYTE *p32 = (BYTE *)bm.bmBits;
    for (y = 0; y < bm.bmHeight; y++) // parcourt toutes les lignes de l'image, de haut en bas
    {
        for (x = 0; x < bm.bmWidth; x++) // parcourt tous les pixels de la ligne, de gauche à droite
        {
            // Recherche une suite continue de pixels non transparents
            int x0 = x;
            LONG *p = (LONG *)(p32 + 4*x);
            while (x < bm.bmWidth)
             {
                if ((unsigned)*p==cTransparentColor)
                  break; // ce pixel est transparent
                p++;
                x++;
            }
            if (x > x0)
            {
                // ajoute les pixels (de (x0, y) à (x, y+1)) à la region en tant que rectangle
                if (pData->rdh.nCount >= maxRects)
                {
                   GlobalUnlock(hData);
                   maxRects += ALLOC_UNIT;
                   hData = GlobalReAlloc(hData, sizeof(RGNDATAHEADER) +
                         (sizeof(RECT) * maxRects), GMEM_MOVEABLE);
                   pData = (RGNDATA *)GlobalLock(hData);
                }
                RECT *pr = (RECT *)&pData->Buffer;
                SetRect(&pr[pData->rdh.nCount], x0, y, x, y+1);
                if (x0 < pData->rdh.rcBound.left)
                   pData->rdh.rcBound.left = x0;
                if (y < pData->rdh.rcBound.top)
                   pData->rdh.rcBound.top = y;
                if (x > pData->rdh.rcBound.right)
                   pData->rdh.rcBound.right = x;
                if (y+1 > pData->rdh.rcBound.bottom)
                   pData->rdh.rcBound.bottom = y+1;
                pData->rdh.nCount++;

                // Il parait que sous Windows 98, ExtCreateRegion() ne marche pas s'il y a trop de rectangles
                // Pas de panique: on construit la region en deux fois
                if (pData->rdh.nCount == 2000)
                {
                    HRGN h = ExtCreateRegion(NULL, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects), pData);
                    if (hRgn)
                    {
                       CombineRgn(hRgn, hRgn, h, RGN_OR);
                       DeleteObject(h);
                    }
                    else
                        hRgn = h;
                    pData->rdh.nCount = 0;
                    SetRect(&pData->rdh.rcBound, MAXLONG, MAXLONG, 0, 0);
                }
            }
         }
        // On passe à la ligne suivante
        p32 += bm.bmWidthBytes;
    }
    // On cree la region
    // (et, s'il y avait plus de 2000 rectangles, on la combine avec celle créee precedemment)
    HRGN h = ExtCreateRegion(NULL, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects), pData);
    if (hRgn)
    {
        CombineRgn(hRgn, hRgn, h, RGN_OR);
        DeleteObject(h);
    }
    else hRgn = h;
    LocalFree((HLOCAL)lpBmpBits);
    return hRgn;
}

Je ne vous demande evidemment pas de la comprendre, mais juste de savoir l'utiliser (c'est pour votre bien, hein :p ) : le handle de bitmap doit bien entendu être obtenu par le chargement d'une image avec LoadImage() ou LoadBitmap, et la structure COLORREF (la couleur de transparence) par un appel à RGB(). (voir L'affichage)

Testez quand même dans chacun des cas si la région obtenue vaut NULL ou pas (retour des fonctions si la création de la région a échoué).

Application de la région à une fenêtre

Alors là, il n'y a rien de plus simple, un appel de SetWindowRgn() avec comme paramètres le handle de la fenêtre à laquelle on veut appliquer la région, le handle de la région à appliquer, et enfin le "Redraw Flag" (booléen à fixer à TRUE si la fenêtre doit être redessinée, ou FALSE sinon).

Exemple

Voici un code illustratif de la manipulation des régions (non compilable) :

HRGN rectRegion,
     roundRectRegion,
     ellipticRegion,
     polygonRegion,
     combinedRegion,
     bitmapRegion;
RECT rect = {10,10,300,100};
POINT pointArray[5];
//Soit bitmap ayant été chargé au préalable
//Soit hwnd ayant été créé au préalable

rectRegion = CreateRectRgnIndirect(&rect);

/* La plupart du temps, les deux dernières valeurs sont déterminées "au feeling" ;) */
roundRectRegion = CreateRoundRectRgn(10,10,300,100,10,10);

ellipticRegion = CreateEllipticRgnIndirect(&rect);

pointArray[0].x = 0; pointArray[0].y = 1;
pointArray[1].x = 3; pointArray[1].y = 3;
pointArray[2].x = 6; pointArray[2].y = 1;
pointArray[3].x = 5; pointArray[3].y = -2;
pointArray[4].x = 2; pointArray[4].y = -2;

polygonRegion = CreatePolygonRgn(pointArray, 5, ALTERNATE);

//RGN_XOR, par exemple
CombineRgn(combinedRegion, rectRegion, ellipticRegion, RGN_XOR);

//Le blanc est la couleur transparente
bitmapRegion = BmpToRgn(bitmap, RGB(255,255,255));

//Et enfin :
SetWindowRgn(hwnd, /* region */);

Si vous n'avez plus besoin d'une région, libérez-la avec DeleteObject(). Pensez-y...

Si vous utilisez BmpToRgn(), n'allez pas croire que cette fonction affiche l'image en même temps. (regardez le prototype... est-ce que vous voyez un HDC ? ^^ ) Bien entendu, vous devrez l'afficher postérieurement, en utilisant les méthodes décrites au chapitre L'affichage.

Rédacteur : Mg++

Gestion de la fenêtre

Maintenant que vous avez une fenêtre taillée par vos soins (dans la majorité des cas, sans barre de titre), peut-être voudriez-vous donner la possiblité à l'utilisateur de la bouger ? :-°

Pour cela, nous allons utiliser une notification très pratique (car ne servant fort heureusement pas qu'à ça), qui n'est autre que WM_NCHITTEST.

Cette notification est envoyée lorsqu'un événement, dont la source est la souris, survient sur la fenêtre d'un programme. Il s'agit d'un test antérieur aux messages WM_MOUSEMOVE, WM_LBUTTONDOWN etc. (tout comme WM_KEYDOWN est antérieur à WM_CHAR après test), pour déterminer dans quelle zone de la fenêtre se trouve la souris. Une fois traitée (si c'est le cas), nous devons renvoyer une constante indiquant la zone de la fenêtre correspondante (après test des coordonnées, forcément).

Ici, la constante à retourner est HTCAPTION (caption pour titre). Cependant, d'autres constantes peuvent être envoyées, représentant d'autres zones de la fenêtre :

  • HTBOTTOM : bordure inférieure (agrandissement vertical possible de la fenêtre)

  • HTBOTTOMLEFT/HTBOTTOMRIGHT : bordure inférieure gauche/droite (agrandissement possible de la fenêtre)

  • HTLEFT/HTRIGHT : bordure gauche/droite (agrnadissement horizontal possible de la fenêtre)

  • HTMAXBUTTON/HTMINBUTTON : bouton "maximiser"/"minimiser"

  • HTCLOSE : bouton de clôture

  • HTGROWBOX - HTSIZE : zone de redimensionnement

  • HTHELP : bouton d'aide

  • etc.

Par contre, si la zone visée n'est tout simplement qu'une zone client, vous devez renvoyer HTCLIENT (logique).

Toutes les possiblités de WM_NCHITTEST, comme celles citées ci-dessus, ne sont pas exposées ici, car ce n'est pas le but de cette partie, mais rien ne vous empêche d'aller vous renseigner sur msdn.

Comment déterminer dans quelle zone le curseur se trouve ?

Je l'ai dit plus haut, il faut tester les coordonnées du curseur. Pour cela, pas besoin de GetCursorPos(), elles se trouvent dans le LPARAM du message (le WPARAM étant non-utilisé). Par conséquent, vous pourrez les récupérer à l'aide des macros GET_X_LPARAM() et GET_Y_LPARAM(), ou MAKEPOINTS(), expliquées antérieurement.

Attention ! Les coordonnées retournées sont relatives au coin supérieur gauche de l'écran, et non à la zone client de la fenêtre (normal, puisque la notification est envoyée avant même que la fenêtre ne traite l'événement correspondant). Vous devrez donc utiliser la fonction GetWindowRect() de prototype :

BOOL GetWindowRect(
    HWND hWnd, //Fenêtre visée
    LPRECT lpRect //Pointeur de RECT
);

Ainsi, vous pourrez récupérer les coordonnées du curseur, relatives à la zone client de la fenêtre, par une bête soustraction des coordonnées contenues dans WM_NCHITTEST par celles retournées par GetWindowRect().

Exemple

/* Soit hwnd le handle de la fenêtre, et lParam le LPARAM de la callback */

//WindowProc avec switch du MSG
case WM_NCHITTEST:
{
    LRESULT ret = HTCLIENT;

    //Coordonnées de la fenêtre
    RECT wCoord;
    GetWindowRect(hwnd, &wCoord);

    //Coordonnées relatives à la zone client de la fenêtre
    POINT cCoord;
    cCoord.x = GET_X_LPARAM(lParam) - wCoord.left;
    cCoord.y = GET_Y_LPARAM(lParam) - wCoord.top;

    //tests des coordonnées
    if(/* zone dite "de déplacement" */)
        ret = HTCAPTION;

    //...

    return ret;
}

Avec cette méthode, votre fenêtre devrait pouvoir bouger fluidement (en tout cas, autant qu'avec des barres de titre "normales" ;) )
Rédacteur : Mg++

Vous êtes d'orénavant capables de tailler vos fenêtres comme bon vous semble, tout en personnalisant sa gestion (redimensionnements, déplacements etc.). Dans une prochaine mise à jour du chapitre, nous verrons quelque chose de nettement plus intéressant, à savoir les listbox dites "ownerdraw", les boutons hover (boutons changeant d'état en fonction du stade de la souris), en utilisant les masques etc., et sûrement à partir de l'élaboration d'une hiérarchie de classes, etc.

Le cours n'est pas terminé, d'autres leçons suivront ;)

Les événements Documentation
Grandes lignes
  1. Les bases
  2. Les contrôles
  3. L'affichage
  4. TP : Un lecteur audio
  5. Les événements
  6. Esthétique avancée

L'auteur

pasquestion etpuisquoiencore


Cours sur L'API Winodos de openclassrooms

Documentation

Vous trouverez ici une mini-documentation remplie au fur et à mesure de l'avancement du tuto.

Fonctions

Voici la liste de toutes les fonctions de l'API Windows que vous avez découvertes dans ce tutoriel.
Pour chaque fonction, j'explique le rôle, ses paramètres et sa valeur de retour.

WinMain

int WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow
);
Description

Cette fonction est le point d'entrée de tout programme Windows, c'est elle qui est appelée en premier dans votre programme. C'est en quelque sorte l'équivalent du main en programme console.

Paramètres
HINSTANCE hInstance

Ce paramètre est l'instance de votre programme, c'est-à-dire une sorte d'identifiant unique qui représente votre programme. Ce paramètre est très important, puisqu'il est utilisé dans de nombreuses fonctions ou structures comme WNDCLASS ou CreateWindow.

HINSTANCE hPrevInstance

Ce paramètre est obsolète depuis la fin de Windows 16-bit, il est toujours mis à NULL.

LPSTR lpCmdLine

Ce paramètre est une chaîne de caractères qui contient une copie conforme de la ligne de commande. Ainsi, contrairement aux programmes console, la ligne de commande n'est pas découpée en arguments mais conservée telle quelle.

int nCmdShow

Ce paramètre a un lien avec ShowWindow, puisqu'il représente l'état dans lequel doit être affichée la fenêtre du programme : maximisée, minimisée,...

Valeur de retour

Si le WinMain se termine avant la boucle des messages (voir GetMessage), alors elle doit retourner 0.
Sinon, lorsqu'elle reçoit le message WM_QUIT, elle doit renvoyer la valeur contenue dans wParam (voir l'exemple dans la première partie).

RegisterClass

ATOM RegisterClass(
    CONST WNDCLASS *lpWndClass
);
Description

Cette fonction enregistre une classe de fenêtre pour une utilisation avec CreateWindow ou CreateWindowEx.
Cette fonction a maintenant été remplacée par une autre, plus puissante : RegisterClassEx, mais elle peut très bien continuer à être utilisée.

Paramètres

Le seul paramètre de cette fonction est un pointeur vers une structure de type WNDCLASS. Pour plus de précision, voir la description de la structure WNDCLASS.

Valeur de retour

Si la fonction réussit, la valeur retournée est un identifiant unique qui représente cette classe.
Si elle échoue, elle renvoie 0.

CreateWindow

HWND CreateWindow(
    LPCTSTR lpClassName,
    LPCTSTR lpWindowName,
    DWORD dwStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    HMENU hMenu,
    HINSTANCE hInstance,
    LPVOID lpParam
);
Description

Cette fonction, comme son nom l'indique, sert à créer une fenêtre.

Sous Windows, il faut prendre le terme fenêtre au sens large : un contrôle (par exemple un bouton) est aussi une fenêtre.

Paramètres
LPCTSTR lpClassName

Ce paramètre est le nom d'une classe précédemment enregistrée avec RegisterClass.

Il existe certaines classes prédéfinies pour les contrôles comme
"BUTTON" pour un bouton.
Voir la liste en annexe.

LPCTSTR lpWindowName

Ce paramètre est le nom de la fenêtre lorsque l'on crée une "vraie" fenêtre.
Lorsqu'il s'agit d'un contrôle, ce paramètre peut être utile (ou non). Voilà une liste non exhaustive :

  • Bouton : texte du bouton

  • Liste : inutile

  • Zone d'édition : texte initial

DWORD dwStyle

Ce paramètre représente le style de la fenêtre, il résulte en fait de la combinaison de nombreuses styles.
Vous pourrez trouver une liste en annexe.
Il existe une série de styles qui s'appliquent à toutes les fenêtres, et d'autres qui ne s'appliquent qu'à certains contrôles.

int x,
    int y,
    int nWidth,
    int nHeight

Ces paramètres représentent la position et la taille initiales de la fenêtre.
Si x=CW_USEDEFAULT, alors le système choisit automatiquement la position de la fenêtre.

CW_USEDEFAULT n'est autorisée que si la fenêtre a le style WS_OVERLAPPED, ou un style qui inclut WS_OVERLAPPED comme WS_OVERLAPPEDWINDOW.

Si nWidth=CW_USEDEFAULT, alors le système choisit automatiquement la taille par défaut de la fenêtre.

La même remarque que ci-dessus s'applique ici.

HWND hWndParent

Ce paramètre est l'identifiant du parent de la fenêtre à créer.
Si ce paramètre vaut NULL, la fenêtre a pour parent le Bureau.

HMENU hMenu

Ce paramètre, lors de la création d'une "vraie" fenêtre, est l'identifiant d'un menu qui s'affichera en haut de la fenêtre (vous savez comme 'Fichier', 'Édition', 'Aide', ...).
Lors de la création d'un contrôle, celui-ci représente un entier utilisé pour identifier le contrôle sans son HWND.
Par exemple:

HMENU identifiantControle=(HMENU)1000;

Il est conseillé de lui donner une valeur supérieure à 100 pour éviter les conflits avec le système.

Si ce paramètre vaut NULL, la fenêtre n'a pas de menu, ou pas d'identifiant selon le cas.

HINSTANCE hInstance

Ce paramètre est l'instance du programme qui crée la fenêtre ; ici en l'occurrence, le vôtre qui est passé en paramètre à WinMain.

LPVOID lpParam

Ce paramètre est un pointeur vers ce que vous voulez, il est utilisé pour passer un paramètre supplémentaire lors de la création de la fenêtre.
Voir la notification WM_CREATE en annexe.

Valeur de retour

Cette fonction renvoie un handle (poignée) sur votre fenêtre, c'est-à-dire une sorte d'identifiant unique, qui vous servira à communiquer avec votre fenêtre pour (par exemple) créer des contrôles dans votre fenêtre, ou changer sa taille.
Si la valeur de retour vaut NULL, alors la création a échoué.

ShowWindow

BOOL ShowWindow(
    HWND hWnd,
    int nCmdShow
);
Description

Cette fonction sert à modifier la visibilité d'une fenêtre.

Paramètres
HWND hWnd

Ce paramètre est le handle sur la fenêtre dont vous voulez modifier la visibilité.
Vous l'obtenez lors de l'appel à CreateWindow par exemple.

int nCmdShow

Ce paramètre peut prendre plusieurs valeurs selon la visibilité que vous voulez appliquer.

  • SW_HIDE : cache la fenêtre (celle-ci deviendra invisible à l'utilisateur).

  • SW_MAXIMIZE : la fenêtre prendra la taille de l'écran.

  • SW_MINIMIZE : la fenêtre sera réduite en barre des tâches.

  • SW_RESTORE : l'inverse de WS_MINIMIZE, la fenêtre retrouvera son état précédent.

  • SW_SHOW : rend la fenêtre visible.

  • SW_SHOWDEFAULT : trop compliqué pour l'instant, il faut avoir une connaissance plus approfondie de Windows pour le comprendre.

  • SW_SHOWMAXIMIZED : c'est équivalent à SW_SHOW + SW_MAXIMIZE.

  • SW_SHOWMINIMIZED : c'est équivalent à SW_SHOW + SW_MINIMIZE

  • SW_SHOWMINNOACTIVE : idem que SW_SHOWMINIMIZED, mais la fenêtre n'est pas activée.

  • SW_SHOWNA : idem que SW_SHOW mais la fenêtre n'est pas activée.

  • SW_SHOWNOACTIVATE : idem que SW_SHOWNORMAL, mais la fenêtre n'est pas activée.

  • SW_SHOWNORMAL : active et montre la fenêtre, si celle-ci est minimisée, cela équivaut à SW_RESTORE.

Je fais une petite remarque :
vous avez sûrement remarqué que j'utilise le mot activé en parlant des fenêtres.
Qu'est-ce que cela veut dire ?
Simplement sous Windows, il y a UNE seule fenêtre active à un moment donné, on appelle ça le focus. C'est elle qui reçoit les événements venant du clavier et de la souris.

Valeur de retour

Cette fonction renvoie TRUE si elle a réussi, et FALSE si elle a échoué.

UpdateWindow

BOOL UpdateWindow(
  HWND hWnd
);
Description

Cette fonction met à jour la zone client de la fenêtre, en la forçant à être redessinée en envoyant un message WM_PAINT directement à la fenêtre, au lieu de placer le message dans la file des messages.

Paramètres
HWND hWnd

handle de la fenêtre dont la zone client est à redessiner.

Valeur de retour

La fonction renvoie TRUE en cas de succès, et FALSE en cas d'échec.

GetMessage

BOOL GetMessage(
    LPMSG lpMsg,
    HWND hWnd,
    UINT wMsgFilterMin,
    UINT wMsgFilterMax
);
Description

Cette fonction récupère un message dans la file du thread appelant.

La précision est importante, car si votre programme possède plusieurs thread, il faut faire attention à qui gère l'interface.

Paramètres
LPMSG lpMsg

Ce paramètre est un pointeur vers une structure de type MSG (voir annexe) qui contiendra, après l'appel à la fonction, le message récupéré.

HWND hWnd

Ce paramètre représente l'identifiant de la fenêtre dont on veut récupérer le message.
S'il ce paramètre vaut NULL, alors la fonction récupère un message qui appartient à n'importe quelle fenêtre créée par le thread.

L'intérêt de paramètre est de permettre une granularité plus fine sur les messages que l'on veut faire passer.

UINT wMsgFilterMin,UINT wMsgFilterMax

Ces paramètres représentent respectivement les identifiants des messages les plus "bas" et les plus "hauts" à faire passer.
Si ces paramètres valent 0, tous les messages sont passés.
Valeurs particulières :

  • WM_KEYFIRST, WM_KEYLAST pour tous les événements clavier.

  • WM_MOUSEFIRST, WM_MOUSELAST pour tous les événements souris.

Valeur de retour

Cette fonction retourne TRUE si elle réussit, et FALSE si elle échoue.

translatemessage

BOOL TranslateMessage(
    const MSG *lpMsg
);
Description

Cette fonction est uniquement utile pour les messages de type clavier, elle traduit les événements de type Touche Virtuelle (voir note) en événement de types Caractère (voir note), et poste le message obtenu dans la file des messages.

Qu'est-ce que c'est donc que ça ?
En fait, lorsque vous appuyez sur une touche, le clavier envoie à Windows un nombre qui est le numéro physique de la touche, ensuite Windows traduit ce numéro en nombre qui est le numéro logique ou virtuel de la touche (les VK_*).
Le problème, c'est que pour les éditeurs de textes, ce n'est pas pratique de savoir quelle touche a été enfoncée ; ce qui les intéressent, c'est le caractère correspondant à cette touche.
C'est pourquoi cette fonction fait la traduction.

Paramètres
const MSG *lpMsg

Ce paramètre est un pointeur vers le message récupéré par GetMessage.

Valeur de retour

Si le message a été traduit, cette fonction renvoie TRUE.
Si le message est WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN ou WM_SYSKEYUP, cette fonction renvoie TRUE.
Sinon, elle renvoie FALSE.

DispatchMessage

LRESULT DispatchMessage(
    const MSG *lpmsg
);
Description

Cette fonction envoie le message à la fonction de callback de la fenêtre concernée.
En quelque sorte, c'est elle qui appelle votre fonction qui gère les messages.

Paramètres
const MSG *lpMsg

Ce paramètre est un pointeur vers le message récupéré par GetMessage.

Valeur de retour

Cette fonction renvoie la valeur retournée par votre fonction de callback.
Par exemple, si dans votre de fonction de gestion des messages vous faites : "return 1000;",
alors, cette fonction retournera 1000.

WindowProc

LRESULT CALLBACK WindowProc
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
);
Description

Ceci n'est pas vraiment une fonction de l'API Windows à proprement parler : c'est la fonction de gestion des messages envoyée à votre (vos) fenêtre(s).

Paramètres
HWND hwnd

Ceci est le handle de la fenêtre concernée par le message.

UINT uMsg

Ceci est l'identifiant du message.

WPARAM wParam,
    LPARAM lParam

Ces paramètres sont entièrement dépendant du message envoyé.
Voir l'annexe sur les messages.

Valeur de retour

Ceci est la valeur retournée par vous-mêmes, ou la fonction DefWindowProc.

DefWindowProc

LRESULT DefWindowProc(
    HWND hWnd,
    UINT Msg,
    WPARAM wParam,
    LPARAM lParam
);
Description

Cette fonction appelle la fonction de gestion des messages par défaut du système. Elle est utile lorsque vous ne gérez pas vous-mêmes le message.
En pratique, à chaque fois que vous ne gérez pas un message, vous appelez cette fonction.

Paramètres

Vous devez passer les paramètres de votre fonction de gestion des messages (voir WindowProc) tels quels..

Valeur de retour

Ceci est la valeur retournée par la fonction de gestion des messages. Vous devez retourner cette valeur :

return DefWindowProc(hwnd,uMsg,wParam,lParam);

SendMessage

LRESULT SendMessage(
    HWND hWnd,
    UINT Msg,
    WPARAM wParam,
    LPARAM lParam
)
Description

Cette fonction permet d'envoyer un message à une fenêtre ou à un contrôle. C'est-à-dire qu'elle va appeler la fonction de gestion des messages de la fenêtre avec les paramètres que vous spécifiez.
Par exemple, si vous faites :

SendMessage(hwnd,WM_QUIT,0,0);

Ceci va appeler une fonction de type WindowProc, associée à hwnd, avec exactement les mêmes paramètres que vous avez spécifiés.

Paramètres

Ce sont les même que pour WindowProc sauf que hWnd est l'identifiant de la fenêtre à laquelle vous envoyez le message.

Valeur de retour

Cette fonction renvoie la valeur retournée par la fonction de gestion des messages.

MessageBox

int MessageBox(
    HWND hWnd,
    LPCTSTR lpText,
    LPCTSTR lpCaption,
    UINT uType
);
Description

Cette fonction extrêmement pratique permet d'afficher une petite fenêtre avec du texte dedans pour informer l'utilisateur, ou lui demander quelque chose.

Par exemple, lorsque vous fermez votre éditeur de texte préféré, vous obtenez un message :
Voulez-vous sauvegarder votre travail ?
Et un choix Oui / Non. Eh bien vous pouvez faire ceci avec MessageBox.

Paramètres
HWND hWnd

Ce paramètre est le handle de la fenêtre qui sera le parent du message affiché.

Mais quel intérêt ?
Vous savez que quand ce message s'affiche dans votre éditeur de texte préféré, vous ne pouvez plus activer votre éditeur de texte, sinon vous obtenez un 'Beeeeeep'.
Eh bien c'est parce que le parent du message est la fenêtre de votre éditeur de texte.

LPCTSTR lpText

Ce paramètre est la chaîne de caractères à afficher dans le message.

Si dans la chaîne de caractères vous mettez '\n', alors cela passera à la ligne suivante comme dans la console.

LPCTSTR lpCaption

Ce paramètre est la chaîne de caractères à afficher comme titre de la fenêtre.

Si dans la chaîne de caractères vous mettez '\n', le résultat est indéfini.

UINT uType

Ce paramètre est le type de message à afficher.
Il peut être la combinaison de plusieurs paramètres grâce au OU binaire(|).
Le premier groupe de paramètres spécifie les boutons à afficher :

  • MB_ABORTRETRYIGNORE : trois boutons : 'Abandonner', 'Réessayer', 'Ignorer'.

  • MB_OK : un bouton : 'Ok'.

  • MB_OKCANCEL : deux boutons : 'Ok', 'Annuler'.

  • MB_RETRYCANCEL : deux boutons : 'Réessayer', 'Annuler'.

  • MB_YESNO : deux boutons : 'Oui', 'Non'.

  • MB_YESNOCANCEL : trois boutons : 'Oui', 'Non', 'Annuler'.

Le deuxième groupe spécifie l'icône à afficher :

  • MB_ICONEXCLAMATION : un point d'exclamation.

  • MB_ICONWARNING : un point d'exclamation.

  • MB_ICONINFORMATION : un i dans un cercle.

  • MB_ICONASTERISK : un i dans un cercle.

  • MB_ICONQUESTION : un point d'exclamation (ce paramètre est déconseillé par Microsoft).

  • MB_ICONSTOP : une icône STOP.

  • MB_ICONERROR : une icône d'erreur.

  • MB_ICONHAND : une icône de main.

Le troisième groupe spécifie le bouton par défaut.

C'est quoi le bouton par défaut ?
Vous savez, quand vous avez un message et que vous appuyez sur Entrée, cela "appuie" sur le bouton qui apparaît différent des autres.

  • MB_DEFBUTTON1 : le premier bouton de la liste.

  • MB_DEFBUTTON2 : le deuxième bouton de la liste.

  • MB_DEFBUTTON3 : le troisième bouton de la liste.

  • MB_DEFBUTTON4 : le quatrième bouton de la liste.

Le quatrième groupe spécifie le type de fenêtre :

  • MB_APPLMODAL : l'utilisateur doit répondre, mais peut activer les autres fenêtres du Bureau, sauf la fenêtre parent du message.

  • MB_SYSTEMMODAL : même chose que MB_APPLMODAL, mais si la fenêtre parent n'est pas la fenêtre au premier plan, elle le devient, et y reste jusqu'à ce que l'utilisateur réponde.

  • MB_TASKMODAL : même chose que MB_APPLMODAL, mais si la fenêtre parent vaut NULL, toutes les fenêtres du thread appelant sont désactivées en attendant la réponse.

Le dernier groupe spécifie d'autres options :

  • MB_RIGHT : le texte est justifié à droite.

  • MB_RTLREADING : le texte est affiché de droite à gauche.

  • MB_SETFOREGROUND : le fenêtre du message passe au premier plan.

  • MB_TOPMOST : la fenêtre du message passe au premier plan, et le reste jusqu'à ce que l'utilisateur réponde.

Valeur de retour

Si la fonction échoue, elle renvoie 0.
Si elle réussit, elle renvoie:

  • IDABORT : l'utilisateur a pressé 'Abandonner'.

  • IDCANCEL : l'utilisateur a pressé 'Annuler'.

  • IDCONTINUE : l'utilisateur a pressé 'Continuer'.

  • IDIGNORE : l'utilisateur a pressé 'Ignorer'.

  • IDNO : l'utilisateur a pressé 'Non'.

  • IDOK : l'utilisateur à pressé 'Ok'.

  • IDRETRY : l'utilisateur à pressé 'Réessayer'.

  • IDTRYAGAIN : l'utilisateur à pressé 'Essayer encore'.

  • IDYES : l'utilisateur à pressé 'Oui'.

CreateMenu

HMENU CreateMenu(VOID);
Description

Cette fonction permet de créer un menu. Ce menu est initialement vide.

Paramètres
Valeur de retour

Le handle du menu en cas de succès, et NULL en cas d'échec.

AppendMenu

BOOL AppendMenu(
    HMENU hMenu,
    UINT uFlags,
    UINT_PTR uIDNewItem,
    LPCTSTR lpNewItem
);
Description

Cette fonction permet d'ajouter un élément à un menu existant.

Par élément, on entend aussi bien un libellé (texte) qu'une case à cocher, une image ou un sous-menu.

Paramètres
HMENU hMenu

Le handle du menu auquel on va ajouter l'élément.

UINT uFlags

Ce paramètre peut être la combinaison d'un ou plusieurs de ces paramètres :

  • MF_BITMAP : utilise une image comme élément ; dans ce cas, lpNewItem contiendra alors le handle de l'image.

  • MF_CHECKED : place une case à cocher à gauche de l'élément.

  • MF_DISABLED : désactive l'élément, il ne peut être sélectionné ; mais ce paramètre ne le fait pas apparaître comme grisé.

  • MF_ENABLED : active l'élément, il peut être sélectionné et n'est pas grisé.

  • MF_GRAYED : désactive l'élément et le fait apparaître grisé.

  • MF_MENUBARBREAK : place l'élément dans une nouvelle colonne.

  • MF_MENUBREAK : même chose que MF_MENUBARBREAK

  • MF_OWNERDRAW : l'élément est dessiné par l'utilisateur.

  • MF_POPUP : l'élément ouvre un menu déroulant.

  • MF_SEPARATOR : l'élément est un séparateur, il ne peut être sélectionné ni mis en surbrillance ; dans ce cas, lpNewItem et uIDNewItem sont ignorés.

  • MF_STRING : l'élément est un libellé (texte) ; dans ce cas, lpNewItem est pointeur sur le texte à afficher.

  • MF_UNCHECKED : l'élément n'a pas de case à cocher à sa gauche (c'est le comportement par défaut).

Les groupes suivants ne peuvent être utilisés ensemble :

  • MF_BITMAP, MF_STRING et MF_OWNERDRAW

  • MF_CHECKED et MF_UNCHECKED

  • MF_DISABLED, MF_ENABLED et MF_GRAYED

  • MF_MENUBARBREAK et MF_MENUBREAK

UINT_PTR uIDNewItem

Spécifie soit l'identifiant du menu, ou si uFlags est mis à MF_POPUP, le handle du sous-menu.

LPCTSTR lpNewItem

Ce paramètre dépend de la valeur de lpNewItem :

  • MF_BITMAP : handle du bitmap.

  • MF_OWNERDRAW : pointeur vers des données utilisateur, utilisables lors du dessin grâce au paramètre lParam.

  • MF_STRING : pointeur sur la chaîne de caractère à afficher.

Valeur de retour

La fonction renvoie TRUE en cas de succès, et FALSE en cas d'échec.

SetMenu

BOOL SetMenu(
    HWND hWnd,
    HMENU hMenu
);
Description

Cette fonction permet de définir le menu associé à une fenêtre.

Paramètres
HWND hWnd

handle de la fenêtre à laquelle on assigne un nouveau menu.

HMENU hMenu

handle du nouveau menu, ou NULL pour que la fenêtre n'ait plus de menu associé.

Valeur de retour

Elle renvoie TRUE en cas de succès, et FALSE en cas d'erreur.

En aucun cas cette fonction ne détruit le menu associé, vous devez appelez destroymenu pour accomplir cette tâche.

EnableMenuItem

BOOL EnableMenuItem(
    HMENU hMenu,
    UINT uIDEnableItem,
    UINT uEnable
);
Description

Cette fonction permet d'activer, désactiver et griser un élément d'un menu.

Paramètres
HMENU hMenu

handle du menu auquel appartient l'élément à modifier.

UINT uIDEnableItem

Ce paramètre est l'indice ou l'identifiant de l'élément du menu à modifier selon le paramètre uEnable.

UINT uEnable

Ce paramètre peut prendre une ou plusieurs de ces valeurs :

  • MF_BYCOMMAND : le paramètre uIDEnableItem spécifie l'identifiant de l'élément (comportement par défaut).

  • MF_BYPOSITION : le paramètre uIDEnableItem spécifie l'indice de l'élément (relatif à 0).

  • MF_DISABLED : désactive l'élément (celui-ci ne sera pas grisé).

  • MF_ENABLED : active l'élément et supprime son attribut grisé.

  • MF_GRAYED : désactive et grise l'élément.

Valeur de retour

La fonction retourne l'état précédent de l'élément modifié (MF_DISABLED, MF_ENABLED ou MF_GRAYED), ou -1 en cas d'échec.

CheckMenuItem

DWORD CheckMenuItem(
    HMENU hmenu,
    UINT uIDCheckItem,
    UINT uCheck
);
Description

Cette fonction permet de définir l'état de la boîte à cocher de l'élément d'un menu.

Paramètres
HMENU hmenu

handle de l'élément auquel appartient le menu.

UINT uIDCheckItem

Ce paramètre est l'indice ou l'identifiant de l'élément du menu à modifier selon le paramètre uEnable.

UINT uEnable

Ce paramètre peut prendre une ou plusieurs de ces valeurs :

  • MF_BYCOMMAND : le paramètre uIDEnableItem spécifie l'identifiant de l'élément (comportement par défaut).

  • MF_BYPOSITION : le paramètre uIDEnableItem spécifie l'indice de l'élément (relatif à 0).

  • MF_CHECKED : coche la boîte à cocher de l'élément.

  • MF_UNCHECKED : décoche la boîte à cocher de l'élément.

Valeur de retour

La fonction retourne l'état précédent de l'élément modifié (MF_CHECKED ou MF_UNCHECKED), ou -1 en cas d'échec.

LoadMenu

HMENU LoadMenu(
    HINSTANCE hInstance,
    LPCTSTR lpMenuName
);
Description

Cette fonction permet de charger un menu contenu dans une ressource de l'exécutable.

Paramètres
HINSTANCE hInstance

Instance du programme qui contient la ressource à charger.

LPCTSTR lpMenuName

Nom de la ressource à charger.
Peut être une chaîne de caractères, ou un identifiant formé avec la macro MAKEINTRESOURCE.

Valeur de retour

Elle renvoie le handle du menu en cas de succès, et NULL en cas d'échec.

CreateDialog

HWND CreateDialog(
    HINSTANCE hInstance,
    LPCTSTR lpTemplate,
    HWND hWndParent,
    DLGPROC lpDialogFunc
);
Description

Cette fonction permet de créer une boîte de dialogue stockée en ressource dans l'exécutable.

Paramètres
HINSTANCE hInstance

Instance du programme auquel appartient la ressource.

LPCTSTR lpTemplate

Nom de la ressource de type boîte de dialogue.
Peut être un pointeur sur une chaîne de caractères, ou une valeur formée par la macro MAKEINTRESOURCE.

HWND hWndParent

handle de la fenêtre parent de la boîte de dialogue.

DLGPROC lpDialogFunc

Pointeur vers la fonction de gestion des messages de la boîte de dialogue.

Valeur de retour

Cette fonction renvoie le handle de la boite de dialogue, ou NULL en cas d'échec.

DialogBox

INT_PTR DialogBox(
    HINSTANCE hInstance,
    LPCTSTR lpTemplate,
    HWND hWndParent,
    DLGPROC lpDialogFunc
);

Cette fonction (qui en fait est une macro) fait la même chose que CreateDialogBox, sauf qu'elle ne rend le contrôle que lors de la destruction de la boîte de dialogue avec la fonction EndDialog.

Paramètres

Ceux sont les mêmes que pour la fonction CreateDialogBox.

Valeur de retour

Cette fonction renvoie la valeur passée au paramètre nResult de la fonction EndDialog.

EndDialog

BOOL EndDialog(
    HWND hDlg,
    INT_PTR nResult
);
Description

Cette fonction détruit la boîte de dialogue, et rend le contrôle au thread lorsque celle-ci a été créée avec la fonction DialogBox qui est bloquante.

Paramètres
HWND hDlg

handle de la boîte de dialogue à détruire.

INT_PTR nResult

Spécifie une valeur à retourner à l'application lors de la destruction.

Cette fonction ne détruit pas instantanément la boîte de dialogue, mais attend que la fonction qui a créé la boîte de dialogue rende le contrôle.

Messages

Voici la liste de tous les messages de l'API Windows que vous avez découverts dans ce tutoriel, et qui sont envoyés à la fonction de gestion des messages .
Pour chaque message, j'expliquerai sa signification ainsi que celle des paramètres wParam et lParam et de la valeur retournée.

WM_CREATE

Description

Ce message est envoyé lors de la création d'une fenêtre avec CreateWindow et CreateWindowEx.

wParam

Ce paramètre est inutilisé.

lParam

Pointeur vers une structure de type CREATESTRUCT.
Voir Annexe sur les structures.

Valeur de retour

Retournez 0 pour continuer la création de la fenêtre, ou -1 pour annuler la création.

WM_DESTROY

Description

Ce message est envoyé lorsqu'une fenêtre va être détruite.

wParam

Ce paramètre est inutilisé.

lParam

Ce paramètre est inutilisé.

Valeur de retour

Retournez 0.

WM_COMMAND

Description

Ce message est utilisé lorsque l'utilisateur interagit avec un contrôle.

wParam

Le mot haut (récupérable avec HIWORD) est le code de notification du contrôle. Celui-ci est spécifique à chaque classe de contrôle. Il vaut 1 si le message vient d'un raccourci clavier, et 0 si le message vient d'un menu.
Le mot bas (récupérable avec LOWORD) est l'identifiant de la fenêtre spécifié lors de la création avec CreateWindow et le paramètre hMenu.

lParam

handle du contrôle (fils de la fenêtre qui reçoit le message) qui a envoyé le message.
Il vaut NULL si ce n'est pas un contrôle.

Valeur de retour

Retournez 0.

Structures

Voici la liste de toutes les structures de l'API Windows que vous avez découvertes dans ce tutoriel.
Pour chaque structure, j'expliquerai le rôle de chacun des membres.

WNDCLASS

typedef struct {
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra;
    int cbWndExtra;
    HINSTANCE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCTSTR lpszMenuName;
    LPCTSTR lpszClassName;
} WNDCLASS, *PWNDCLASS;
Description

Cette structure est utilisée lors de la création d'une classe de fenêtre.
Elle est passée en paramètre à RegisterClass.

Membres
UINT style

Spécifie les styles de la classe, ce peut être une combinaison avec le OU binaire (|) de n'importe quel paramètre :

  • CS_BYTEALIGNCLIENT : aligne la zone client des fenêtres sur la frontière d'un octet. Ce style affecte la taille et la position horizontale des fenêtres.

  • CS_BYTEALIGNWINDOW : même chose que CS_BYTEALIGNCLIENT, mais concernant la zone fenêtre, et non pas sa zone client.

  • CS_CLASSDC : les fenêtres de cette classe partagent toutes le même DC (device context) utilisé lorsque l'on dessine dans les fenêtres.

  • CS_DBLCLKS : les fenêtres reçoivent des événements de type double-clic.

  • CS_GLOBALCLASS : cette classe est globale au système et non pas locale au programme.

  • CS_HREDRAW : redessine la fenêtre en cas de changement de largeur des fenêtres.

  • CS_NOCLOSE : la fenêtre ne peut pas être fermée.

  • CS_OWNDC : chaque fenêtre à son propre DC.

  • CS_PARENTDC : permet d'optimiser le dessin dans le DC en récupérant le DC du parent et en empêchant le dessin en dehors de sa surface.

  • CS_SAVEBITS : lorsqu'une portion de fenêtre est cachée par une autre, sauvegarde cette portion puis la restaure ultérieurement au lieu de redessiner.

  • CS_VREDRAW : redessine la fenêtre en cas de changement de hauteur.

Mais qu'est-ce qu'un DC ?
Un DC (Device Context) est utilisé lors du dessin dans la zone client d'une fenêtre.
Par exemple, lorsque vous créez des boutons dans une fenêtre, ceux-ci sont dessinés dans des DC, puis ces DC sont copiés sur le DC du parent qui est ensuite affiché.
On peut voir un DC comme une zone de dessin.
Le paramètre CS_PARENTDC permet, au lieu de copier les DC des fils dans celui du parent (ce qui est lent), de dessiner directement le fils dans le DC du parent.
Les DC sont principalement utilisé avec les fonctions du GDI qui permettent de dessiner des rectangles, carrés, du texte, des polygones, etc.

WNDPROC lpfnWndProc

Ce membre est un pointeur vers la fonction de gestion des messages arrivant à toutes les fenêtres de cette classe.

int cbClsExtra

Spécifie le nombre d'octets supplémentaires à allouer dans la structure de la classe.
Cela permet de stocker des données supplémentaires associées à cette classe.

int cbWndExtra

Spécifie le nombre d'octets supplémentaires à allouer dans la structure d'une fenêtre.
Cela permet de stocker des données supplémentaires associées à chaque fenêtre.

HINSTANCE hInstance

Instance du programme qui crée la classe.
Cette variable est passée en paramètre au WinMain.

HICON hIcon

handle de l'icône à afficher dans la barre de titre des fenêtres de cette classe.

HCURSOR hCursor

handle du curseur à afficher quand le pointeur de la souris est au-dessus de la fenêtre.

HBRUSH hbrBackground

Brosse à utiliser pour dessiner le fond de la fenêtre (c'est en rapport avec le GDI).
Il y en a quelques-unes prédéfinies :

  • COLOR_ACTIVEBORDER

  • COLOR_ACTIVECAPTION

  • COLOR_APPWORKSPACE

  • COLOR_BACKGROUND

  • COLOR_BTNFACE

  • COLOR_BTNSHADOW

  • COLOR_BTNTEXT

  • COLOR_CAPTIONTEXT

  • COLOR_GRAYTEXT

  • COLOR_HIGHLIGHT

  • COLOR_HIGHLIGHTTEXT

  • COLOR_INACTIVEBORDER

  • COLOR_INACTIVECAPTION

  • COLOR_MENU

  • COLOR_MENUTEXT

  • COLOR_SCROLLBAR

  • COLOR_WINDOW

  • COLOR_WINDOWFRAME

  • COLOR_WINDOWTEXT

Si ce paramètre est NULL, le programme doit dessiner lui-même le fond de la fenêtre lors des notification WM_PAINT ou WM_ERASEBKGND.

LPCTSTR lpszMenuName

Nom de la ressource à utiliser comme menu pour les fenêtres de la classe (voir note).

LPCTSTR lpszClassName

Nom de la classe. Il doit être unique dans le programme pour que la classe soit enregistrée avec succès.

Qu'est-ce qu'une ressource ?
Une ressource est une donnée spéciale intégrée à l'exécutable avec un nom.
Par exemple, on peut intégrer une image, un menu, un raccourci clavier, une fenêtre, etc.
Chaque ressource est identifiée par un nom qui peut être une chaîne de caractères ou un identifiant.
Les ressources peuvent aussi être intégrées à l'exécutable avec un fichier .rc.

MSG

typedef struct {
    HWND hwnd;
    UINT message;
    WPARAM wParam;
    LPARAM lParam;
    DWORD time;
    POINT pt;
} MSG, *PMSG;
Description

Cette structure représente un message dans la file des messages envoyés à une fenêtre.

Membres
HWND hwnd

Ceci est le handle de la fenêtre qui doit recevoir le message.

UINT message

Identifiant du message (c'est le même que celui passé à la fonction de gestion des messages).

WPARAM wParam
LPARAM lParam

Ce sont des paramètres supplémentaires passés à la fonction de gestion des messages.

DWORD time

L'heure à laquelle le message a été posté.

POINT pt

Position du curseur lorsque le message a été posté.

CREATESTRUCT

typedef struct tagCREATESTRUCT {
    LPVOID lpCreateParams;
    HINSTANCE hInstance;
    HMENU hMenu;
    HWND hwndParent;
    int cy;
    int cx;
    int y;
    int x;
    LONG style;
    LPCTSTR lpszName;
    LPCTSTR lpszClass;
    DWORD dwExStyle;
} CREATESTRUCT, *LPCREATESTRUCT;
Description

Cette structure est passée en paramètre avec le message WM_CREATE.
Elle permet de connaître les paramètres exacts avec lesquels la fenêtre a été créée.

Membres

Les membres sont exactement les mêmes que pour CreateWindow.
Seules précisions :

int x int y int cx int cy

Si, lors de la création, vous avez spécifié CW_USEDEFAULT ou 0, la valeur vaudra alors la taille réelle de la fenêtre.

RECT

typedef struct _RECT {
  LONG left;
  LONG top;
  LONG right;
  LONG bottom;
} RECT;
Description

Cette structure permet de stocker différents paramètres qui ont à voir avec des rectangles comme des dimensions, des marges ou des positions...

Membres
LONG left

Coordonnée en abscisse du coin supérieur gauche.

LONG top

Coordonnée en ordonnée du coin supérieur gauche.

LONG right

Coordonnée en abscisse du coin inférieur droit.

LONG bottom

Coordonnée en ordonnée du coin inférieur droit.

La documentation sera remplie au fur et à mesure de l'avancement du tuto, et de notre temps libre. ;)

Esthétique avancée Glossaire
Annexes
  1. Documentation
  2. Glossaire
  3. Manipulation du registre
  4. Divers

L'auteur

pasquestion etpuisquoiencore


Cours sur L'API Winodos de openclassrooms

Glossaire

Voici donc les termes que vous ne connaissez peut être pas. N'hésitez pas à regarder si vous avez des doutes.

Termes

  • msdn : véritable bible du codeur winAPI, c'est en fait la documentation anglaise de Microsoft accessible ici.

  • Bas niveau (langage, fonction, instruction, etc.) : peu développé, proche du système voire du matériel. Exemple : langage bas niveau.

  • Pop-up : se dit d'une fenêtre ou d'un menu apparaissant soudainement après une action de l'utilisateur.

  • Haut niveau : développé, proche de l'utilisateur ou codeur. Exemple : langage haut niveau.

  • Instance : utilisé généralement dans les langages orientés objets, désigne une occurrence d'un objet. Elle est dans ce tuto votre programme en lui-même (c'est un objet après tout). Exemple :

    //variable est une instance de Objet
Objet variable;

  • Fonction callback : fonction chargée de traiter les messages envoyés à une fenêtre, ou à un contrôle. Aussi dénommée procédure. Exemple :

    LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

  • Template, ou modèle : sorte de schéma situé en ressource décrivant l'organisation d'un objet, comme un menu ou une boîte de dialogue. En C++, c'est une fonction applicable à n'importe quel type d'argument. Exemple :

    ID_MENU MENU
BEGIN
        POPUP "Menu"
        BEGIN
                MENUITEM "Commande 1", ID_CMD1
                MENUITEM "Commande 2", ID_CMD2
        END
END

  • Cast, ou transtypage : action de transformer le type d'une variable en une autre. Exemple :

    long lVariable = (long)iVariable

  • Flag, ou drapeau : caractère spécial donné à un objet, pouvant être combiné avec l'opérateur logique |. Généralement utilisé pour les styles. Exemple :

    truc.uFlags = F_TRUC | F_MACHIN;

  • Focus : fait, généralement pour un contrôle, de recevoir tous les évènements clavier. Pour une fenêtre, on parle d'active.

Le glossaire n'est pas fini, il est rempli au fur et à mesure de l'avancement du tuto ;)

Documentation Manipulation du registre
Annexes
  1. Documentation
  2. Glossaire
  3. Manipulation du registre
  4. Divers

L'auteur

pasquestion etpuisquoiencore


Cours sur L'API Winodos de openclassrooms

Manipulation du registre

Vous allez ici apprendre à manier les clés registre en utilisant des fonctions définies dans l'API win32.

A la fin de ce chapitre, vous saurez, en modifiant manuellement (avec le winAPI) ou semi-manuellement (en modifiant les clés à la main) :

  • Lancer votre programme au démarrage de windows

  • Changer l'icône d'un type de fichiers

  • Associer votre programme avec un ou plusieurs types de fichiers

Pour ce faire, lisez ce qui suit attentivement et ne modifiez surtout pas ce que vous ne connaissez pas.

Quelques notions de BDR

Avant de commencer, nous allons vous inculquer quelques notions de BDR.

Mais que diable est-ce, la BDR ?

La BDR est une base de données où sont stockées les informations nécessaires au bon fonctionnement de Windows. On y trouve :

  • les configurations de l'ordinateur (matériel, etc.)

  • la personnalisation de Windows (affichage, sons, etc.)

  • les préférences utilisées pour les programmes et jeux (historiques, tableaux de scores, options, chemins d'accès, etc.)

  • etc.

Ainsi, chaque fois que vous modifiez ne serait-ce qu'une chose (si petite soit elle) dans votre ordinateur, la BDR s'en trouve sensiblement changée.

Les informations sont classées dans des dossiers dans l'arborescence, et se présentent sous la forme de clés registre. Celles-ci sont des informations, possédant un type, un ID et une valeur.

Normalement, les clés registre sont les dossiers présents dans l'arborescence, et les informations des valeurs, mais j'ai préféré garder cette syntaxe pour une meilleure compréhension.

Vous pouvez accéder à la BDR en tapant 'regedit' (comme registry edit) dans la boîte de dialogue 'Exécuter...', présente dans le menu 'Démarrer'.

Pour ceux qui ne l'ont jamais vue, voici sous quelle forme se présente la BDR :

Comme vous pouvez le voir sur cette capture d'écran, les répertoires à la racine de la BDR sont :

  • HKEY_LOCAL_MACHINE : contient les informations communes à tous les utilisateurs de la machine.

  • HKEY_CLASSES_ROOT : contient les informations sur les extensions de fichiers, donc des associations.

  • HKEY_CURRENT_USER : contient les informations (préférences en général) spécifiques à l'utilisateur actuel.

  • HKEY_USERS : contient les paramètres propres à chaque utilisateur.

  • HKEY_CURRENT_CONFIG : contient les configurations de l'utilisateur actuel.

Bien ! Je pense que vous savez maintenant le minimum pour débuter. Direction la prochaine partie.
Rédacteur : Mg++

Changement d'icône de fichiers

Commençons progressivement. Nous allons voir comment, à partir du répertoire racine HKEY_CLASS_ROOT, changer l'icône associée à un type de fichier.

1ère méthode : méthode semi-manuelle (manipulation au travers de regedit)

Voilà comment est organisée la BDR de ce côté-ci : un dossier portant comme nom une extension contient une clé registre (default) portant comme valeur le nom descriptif du fichier. Alors, le dossier portant ce nom descriptif à l'intérieur de HKEY_CLASSES_ROOT (donc un niveau en arrière) contient un dossier intitulé DefaultIcon. Dans celui-ci, la clé (default) porte comme valeur le chemin de l'icône associée à ce type de fichiers.

Il se peut que le chemin de l'icône ne vous porte pas à croire que cela en est un : parfois (souvent en fait pour Windows), ce chemin est en fait le chemin d'accès vers une DLL (Dynamic Link Library), contenant dans ses ressources l'icône demandée. Il est alors suivi d'une virgule, et d'un nombre étant son ID en ressource.

Voici concernant le descriptif de son organisation. Concrètement, les étapes permettant de changer l'icône peuvent être représentées sur un schéma comme ceci :

Comme voir est mieux que croire, je vais vous indiquer pas à pas comment changer l'icône d'un fichier XML (par exemple).

En premier lieu, ouvrez la BDR et rendez-vous dans HKEY_CLASSES_ROOT.
Vous devriez voir ceci :

Ensuite, recherchez le dossier portant comme nom '.xml', et ouvrez-le.

Information bien pratique et souvent oubliée : vous pouvez accéder directement au dossier recherché en tapant les premiers caractères du dossier en question. Vous vous retrouverez avec le dossier trouvé en surbrillance. ;)

Mémorisez ensuite la valeur du (default) contenue dedans. Il s'agit du nom descriptif du fichier concerné.

Vous aurez ceci :

Puis, rendez-vous au dossier portant ce nom descriptif, sachant qu'il est contenu au même niveau de HKEY_CLASSES_ROOT que les extensions, et ouvrez-le. Vous verrez à l'intérieur un dossier intitulé DefaultIcon, le dossier que nous cherchons.
Rendez-vous y et vous pourrez alors contempler le chemin de l'icône concernée, contenu dans la valeur du (default) de ce dossier.

En fait, vous voyez normalement ceci :

Pour modifier l'icône, faites un clic droit sur la clé (default) et sélectionnez Modify. Une boîte de dialogue s'ouvre, et vous pourrez alors sélectionner le chemin de l'icône voulue dans le champ Valeur avant de quitter.

Une illustration :

L'icône est ainsi changée. :)

2ème méthode : méthode manuelle (utilisation du winAPI) dans un code en C

Pour modifier le registre Windows dans votre code, vous aurez besoin d'inclure la bibliothèque nécessaire dans votre code, à savoir :

#include <windows.h>

Celle-ci, seule bibliothèque à intégrer au code pour se servir du winAPI, définit les fonctions permettant de manipuler le registre dont nous aurons besoin :

  • RegOpenKeyEx()

  • RegCreateKeyEx()

  • RegQueryValueEx()

  • RegSetValueEx()

  • RegCloseKey()

Ces fonctions agissent sur une variable de type "handle de clé" ou HKEY.

Maintenant que vous avez les informations nécessaires, allons créer notre code ;) .
Une fonction sera beaucoup plus pratique, pour la maintenance et la lisibilité du code. Je vous propose donc cette forme de fonction :

void changeIcon(const char *ext, const char *icon);

  • ext désigne l'extension dont on veut modifier l'icône

  • icon désigne le chemin de l'icône voulue

Voyons maintenant son contenu :

Les déclarations de variables

  • Le handle de clé :

    HKEY key;

  • Une string qui va nous permettre de mémoriser ce dont nous avons besoin :

    char mem[MAX_PATH] = "";

  • Une variable indiquant la taille de la string récupérée, pour queryValue :

    DWORD kSize /* = 8192 */;

Etapes

  • Ouverture du dossier portant l'extension comme nom :

    RegOpenKeyEx(
             HKEY_CLASSES_ROOT, //Nom de la branche principale
             ext, //Nom de la sous-clé (dossier), ici l'extension
             0, //réservé
             KEY_ALL_ACCESS, //Tout accès
             &key //Taille de la donnée, en bytes
             );

  • Récupération dans mem de la valeur du default :

    RegQueryValueEx(
                key,
                NULL, //NULL, donc valeur de (default)
                0,
                NULL, //Taille de data (inutile)
                (BYTE*)mem, //Buffer de sortie
                &kSize
                );

  • Ouverture du dossier DefaultIcon situé dans celui portant le nom descriptif du fichier :

    strcat(mem, "\\DefaultIcon");
RegOpenKeyEx(
            HKEY_CLASSES_ROOT,
            mem,
            0,
            KEY_ALL_ACCESS,
            &key
            );

  • Remplacement du chemin d'accès à l'icône par celui de l'icône voulue :

    RegSetValueEx(
              key,
              NULL, //(default)
              0,
              REG_SZ, //Type de data
              (BYTE*)icon,
              strlen(icon)+1 //Taille de data
              );

  • On n'oublie pas de fermer la clé :

    RegCloseKey(key);

Nous avons maintenant fait le tour, je pense... Voici donc le code complet de la fonction :

#include <windows.h>

void changeIcon(const char *ext, const char *icon)
{
   HKEY key;
   DWORD kSize;
   char mem[MAX_PATH] = "";

   RegOpenKeyEx(HKEY_CLASSES_ROOT, ext, 0, KEY_ALL_ACCESS, &key);

   RegQueryValueEx(key, NULL, 0, NULL, (BYTE*)mem, &kSize);

   strcat(mem, "\\DefaultIcon");
   RegOpenKeyEx(HKEY_CLASSES_ROOT, mem, 0, KEY_ALL_ACCESS, &key);

   RegSetValueEx(key, NULL, 0, REG_SZ, (BYTE*)icon, strlen(icon)+1);

   RegCloseKey(key);

   return;
}

Je n'explique pas ici comment revenir en arrière. Alors si c'est pour un test, prévoyez une méthode de rattrapage. Ce n'est pas bien compliqué, je vous propose de créer une clé de backup que vous pourrez copier dans le (default) quand vous en aurez marre. Vous avez normalement tout ce qu'il faut pour, mais si vous avez du mal, le forum est là. ;)

Rédacteur : Mg++

Lancement au démarrage de windows

Maintenant que vous avez normalement compris comment manipuler, que cela soit à partir de regedit ou du winAPI, la BDR, inutile de détailler autant. (C'est surtout que je n'ai plus de place pour les images. :p )

Alors dans cette partie, je vais vous apprendre comment lancer votre application au démarrage de Windows. C'est pas bien long, et pas bien compliqué, mais si vous n'avez pas entièrement compris le principe de la BDR ou comment la manier, je vous renvoie à la sous-partie n°2.

1ère méthode : semi-manuelle (au travers de regedit)

Afin d'ajouter un programme à la liste de démarrage (au startup, dans le jargon), il faut juste ouvrir le dossier Software\Microsoft\Windows\CurrentVersion\Run, situé dans la branche d'arborescence HKEY_CURRENT_USER (pour l'utilisateur actuel seulement, c'est préférable), et ajouter une clé registre portant comme ID votre nom de programme, et comme valeur son chemin d'accès.

Pour cela, placez-vous dans le dossier Run dont la place a été citée, faites un clic droit sur la zone d'énumération des clés registre (partie droite), sélectionnez New -> Key et spécifiez comme dit plus haut :

  • comme ID : votre nom de programme (peu importe)

  • comme valeur : son chemin d'accès.

Veillez à ne pas bouger votre programme entre temps, sinon l'ouverture du programme au startup échouera. Pour le parer, vous pouvez le placer dans un répertoire permanent, ou changer la clé à chaque fois.

Vous pouvez aussi spécifier des paramètres à votre exécutable (comme d'habitude), en délimitant le chemin d'accès des arguments (avec des guillemets), et en les plaçant à sa suite entre des espaces.

En ce qui conçerne sa suppression de la liste de démarrage, vous n'avez qu'à vous rendre dans le répertoire Run, et supprimer la clé que vous avez créée.

2ème méthode : manuelle (en utilisant le winAPI)

Pareil ici, rien de compliqué ou de long. Pour faire ce travail, je vous propose la fonction suivante :

void addToStartup (const char *path, const char *name, bool state);

  • path désigne le chemin d'accès à votre programme

  • name désigne le nom de votre programme

  • state est un booléen indiquant si vous voulez l'ajouter, ou le retirer de la liste de démarrage (true pour ajouter, false pour retirer).

Afin d'exécuter ce travail, une petite nouvelle fait son apparition parmi les fonctions que nous utilisons durant ce tuto : RegDeleteValue() .

Voyons maintenant le codage. Nous aurons besoin des variables suivantes :

  • Le handle de clé :

    HKEY key;

  • Et c'est tout ! :)

Ensuite viennent les étapes. Les voici donc :

  • Ouverture du dossier Run pour y inscrire la clé :

    RegOpenKeyEx(
             HKEY_CURRENT_USER,
             "Software\\Microsoft\\Windows\\CurrentVersion\\Run",
             0,
             KEY_ALL_ACCESS,
             &key
             );

  • Inscription de la clé (si state vaut true) :

    RegSetValueEx(
              key,
              name,
              0,
              REG_SZ,
              (BYTE*)path,
              strlen(path)+1
              );

  • Suppression de la clé (si state vaut false) :

    RegDeleteValue(
              key,
              name
              );

  • Fermeture de la clé :

    RegCloseKey(key);

Et maintenant, le code complet :

void addToStartup(const char *path, const char *name, bool state)
{
   HKEY key;

   RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_ALL_ACCESS, &key);

   if(state)
       RegSetValueEx(key, name, 0, REG_SZ, (BYTE*)path, strlen(path)+1);
   else
       RegDeleteValue(key, name);

   RegCloseKey(key)

   return;
}

Et voilà, vous pouvez mettre votre programme au démarrage ;) .
Rédacteur : Mg++

Association de fichiers

Dans cette partie, je vais vous apprendre comment associer votre programme avec un type défini de fichiers.

1ère méthode : semi-manuelle (à l'aide de regedit)

Ici, retour à la branche d'arborescence HKEY_CLASSES_ROOT, puisque c'est là que la gestion des extensions est faite.

Le processus est assez semblable au changement d'icône : un dossier portant l'extension du fichier comporte un (default) pointant vers un autre dossier (contenu au même endroit que l'extension) étant le nom descriptif du type de fichier. Mais comme vous avez pu le voir sur les captures d'écran, il y a d'autres dossiers dans celui-ci, autres que DefaultIcon. Dans un de ceux-ci, Shell, se trouve Open, et dedans se trouve enfin command.

Il se peut que pour certains types de fichiers (comme les fichiers musicaux), Open ne soit pas la seule commande disponible : il peut y avoir notamment Play... Dans ce cas-là, normalement on doit répéter l'opération, mais les entrées sont supprimées, si ce n'est pas le chemin de WMP (alala, Microsoft). Mais si vous voulez plus de précisions sur ces cas-ci, envoyez-moi un MP.

C'est sa valeur (default) qui va déterminer quel programme va lancer ce type de fichier à la commande Open du clic droit d'un ou plusieurs fichiers. En effet, il comporte le chemin d'accès au programme, suivi de soit "%1", si le programme n'ouvre qu'un fichier à la fois, soit "%L", s'ils sont plusieurs et donc listés.

En effet, %1 et %L sont des opérateurs batch : le premier sera remplacé par le seul, ou le premier fichier ouvert (puisqu'il n'en autorise qu'un), alors que l'autre sera remplacé par la liste des fichiers ouverts. Chacun se fait remplacer par des chemins d'accès absolus. Par exemple, "C:\Folder\Program.exe" /open "%1" enverra à l'exécution du programme "C:\Folder\Program.exe" comme premier argument, /open comme deuxième, et le chemin absolu du fichier ouvert en troisième.

Maintenant que vous avez vu l'organisation des associations, voici un exemple de nouvelle association de fichiers. Je vais vous expliquer comment associer Notepad++ aux fichiers .txt par exemple.

Voici la marche à suivre, comme vous l'avez deviné ;) :

  • ouvrez HKEY_CLASSES_ROOT

  • Repérez le dossier portant le nom .txt et ouvrez-le.

  • Mémorisez le (default) contenu à l'intérieur (à savoir txtfile).

  • Retournez dans HKEY_CLASSES_ROOT, et ouvrez txtfile.

  • Ouvrez maintenant successivement Shell, Open, et enfin command.

  • Modifiez la valeur du (default) par le chemin de NotePad++ entre guillemets (puisqu'il n'est pas la seule information), suivi d'un espace et de "%1"

    • Cette manoeuvre n'est qu'à titre indicatif, je ne sais pas si NotePad++ ne possède aucun argument excepté le fichier ouvert.

Voilà, l'association est terminée :)

2ème méthode : manuelle (en utilisant le winAPI)

Passons maintenant au codage.

Cette fois-ci, je vous propose la fonction suivante :

void associate(const char *ext, const char *path);

  • ext désigne l'extension à associer

  • path désigne le chemin d'accès à votre programme

Vous aurez besoin comme d'habitude de :

  • un handle de clé :

    HKEY key;

  • une string tampon :

    char mem[MAX_PATH];

  • Une taille d'info nécessaire à RegQueryValueEx() en bytes :

    DWORD kSize /* = 8192*/;

Pour transcrire cette manoeuvre en code, vous n'aurez ni plus ni moins besoin que des fonctions citées précédemment.

Voici les étapes requises :

  • Ouverture du dossier portant l'extension comme nom :

    RegOpenKeyEx(
             HKEY_CLASSES_ROOT,
             ext,
             0,
             KEY_ALL_ACCESS,
             &key
             );

  • Récupération dans mem de la valeur du default :

    RegQueryValueEx(
                key,
                NULL,
                0,
                NULL,
                (BYTE*)mem,
                &kSize
                );

  • Ouverture du dossier pointé, en précisant la commande d'ouverture du shell :

    strcat(mem, "\\Shell\\Open\\command");
RegOpenKeyEx(
            HKEY_CLASSES_ROOT,
            mem,
            0,
            KEY_ALL_ACCESS,
            &key
            );

  • Remplacement du chemin d'accès par celui de votre programme :

    //Changez le %1 par %L dans le cas d'une sélection multiple possible
sprintf(mem,"\"%s\"/*arguments possibles*/ \"%1\"", path);
RegSetValueEx(
              key,
              NULL,
              0,
              REG_SZ,
              (BYTE*)mem,
              strlen(mem)+1
              );

  • Fermeture du handle de clé :

    RegCloseKey(key);

Et voici la fonction complète (incluant la prise en compte des erreurs) :

Je n'ai pas prévu dans cette fonction de moyen de retour, donc prévoyez un système de copie comme celui que j'ai décrit dans la deuxième partie pour le changement d'icônes au cas où...

void associate(const char *ext, const char *path)
{
   HKEY key;
   DWORD kSize;
   char mem[MAX_PATH] = "";

   if(RegOpenKeyEx(HKEY_CLASSES_ROOT, ext, 0, KEY_ALL_ACCESS, &key) == ERROR_SUCCESS)
   {
        RegQueryValueEx(key, NULL, NULL, NULL, (BYTE*)mem, &kSize);

        strcat(mem, "\\Shell\\open\\command");

        if(RegOpenKeyEx(HKEY_CLASSES_ROOT, mem, 0, KEY_ALL_ACCESS, &key) == ERROR_SUCCESS)
        {
            sprintf(mem,"\"%s\" \"%1\"", path);
            RegSetValueEx(key, NULL, 0, REG_SZ, (BYTE*)mem, strlen(mem)+1);
        }
   }
   RegCloseKey(key);

   return;
}

Et vous avez votre association. :)

Veillez à ne pas bouger votre exécutable après, sinon l'ouverture échouera.

Rédacteur : Mg++

Si vous avez des questions de précision ou des problèmes, n'hésitez pas à poster dans le forum, il est fait pour ça ;) .

Les fonctions n'ont pas été testées mais marchent sans doute possible. Néammoins dans le cas contraire, veuillez m'adresser un MP.

N'oubliez pas de prévoir une solution de suppression pour chacun des cas.

Glossaire Divers
Annexes
  1. Documentation
  2. Glossaire
  3. Manipulation du registre
  4. Divers

L'auteur

pasquestion etpuisquoiencore


Cours sur L'API Winodos de openclassrooms

Divers

Utiliser le style du Windows courant

Vous allez ici apprendre à donner le style de votre Windows actuel à votre application.

Téléchargement préalable

Pour cela, vous aurez besoin d'un fichier manifeste, se présentant sous la forme d'un fichier xml.

Commencez donc par télécharger celui-ci.
(Clic droit + Enregistrer sous...)

Si le téléchargement échoue, sachez qu'il s'agit seulement d'un fichier codé en ASCII (donc avec NotePad, par exemple) et renommé en .xml, contenant le code suivant :

<assembly manifestVersion="1.0">
    <assemblyIdentity version="1.0.0.0" processorArchitecture="X86" name="CompanyName.ProductName.YourApplication" type="win32"/>
    <description>Description de votre application ici.</description>
    <dependency>
        <dependentAssembly>
          <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="X86" publicKeyToken="6595b64144ccf1df" language="*"/>
        </dependentAssembly>
    </dependency>
</assembly>

Association

Pour associer le manifeste à votre application, vous disposez de deux méthodes :

  • dynamiquement, donc pendant l'exécution

  • en utilisant les ressources, ceci pour l'intégrer à l'exécutable.

Dynamiquement

En utilisant cette méthode, l'exécutable utilisera le manifeste situé dans le même dossier que celui-ci.
Commencez par renommer votre manifeste avec le nom de votre exécutable comme radical, et avec ".manifest" comme extension. Par exemple, si votre exécutable s'appelle "Programme.exe", alors renommez "manifest.xml" en "Programme.exe.manifest".

Vous n'avez plus qu'à placer le manifeste renommé dans le répertoire contenant votre exécutable.

En utilisant les ressources

Vous avez peut-être envie de vous débarrasser de quelques fichiers entourant l'exécutable, en les intégrant aux ressources ? Comme le manifeste, par exemple ? :p
Bien. Alors tout se passe à la compilation. Rajoutez à votre script ressource (*.rc) le code d'intégration suivant :

1 24 "manifest.xml"

Dans ce cas-ci, 1 est l'ID du manifeste, 24 son type, et "manifest.xml" son chemin.

Modifiez-le en conséquence s'il ne se trouve pas dans le répertoire des codes sources.

Attention, dans certains cas (je n'en connais pas la raison à ce jour), vous obtiendrez une fenêtre vide. Alors, la procédure à observer est la suivante :

  • Inclure <commctrl.h>.

  • Appeler InitCommonControls() au début du programme.

  • Linker le projet avec comctl32.lib ou libcomctl32.a (selon le compilateur utilisé).

Pour les feignants utilisant Code::Blocks, vous pouvez passer outre ces étapes, simplement en accédant à l'entrée Windows XP look'n'feel via le menu Plugin.

Vous n'avez plus qu'à contempler votre application disposer du style du Windows courant. ;)
Rédacteur : Mg++

La notation hongroise

La notation hongroise est une notation largement employée par Microsoft, qui consiste à faire apparaître, dans le nom de chaque variable, le type de celle-ci.

Les avis sont partagés à son sujet : à savoir si elle est utile ou non, productive ou non, mais toujours est-il qu'elle reste employée, et qu'on a tout intérêt à la comprendre.

Principe

Nous allons donc ici décrire les sigles qui reviennent le plus souvent.
En fait, elle consiste à préfixer en minuscules le nom de la variable (qui commence alors par une majuscule) par le sigle de son type.

Exemple:

HANDLE hWnd;
INT iX;
CHAR cCode;

Les préfixes

Les préfixes utilisés sont:

  • i : entier (int ou INT en général)

  • n : entier court (short int ou SHORT)

  • l : entier long (long int ou LONG)

  • f : nombre à virgule flottante simple précision (float ou FLOAT)

  • d : nombre à virgule flottante double précision (double ou DOUBLE)

  • c : caractère (char ou CHAR) ou un dénombrement

  • ch : caractère (char ou CHAR)

  • b : booléen (bool ou BOOL)

  • s : chaîne de caractères (CHAR *, PCHAR, LPSTR, LPCSTR, ...)

  • sz : chaîne de caractères terminée par un zéro (CHAR *, PCHAR, LPSTR, LPCSTR, ...)

  • w : mot ou "word" en anglais (WORD)

  • dw : double-mot ou "double word" en anglais (DWORD)

  • h : handle (HANDLE, HWND, HFILE, HBITMAP, ...)

  • pt : point (POINT)

  • rgb : couleur (RGB) [rarement utilisée]

  • str : encore une autre façon de préciser une chaîne de caractères.

Les modificateurs

Il existe aussi quelque modificateurs:

  • u : non-signé. Exemple : ul pour ULONG

  • lp, p ou p_ : pointeur sur quelque chose. Exemple : lpstr ou psz...

  • a ou ar : tableau (ou "array" en anglais) [rarement utilisé]

  • m_ : membre d'une classe (C++) ou d'une structure

  • g_ : variable globale

  • s_ : variable statique (correspondant au mot-clé static en C/C++).

Exemples

Ainsi, en mélangeant les deux, on peut obtenir ceci par exemple :

LPSTR g_szName;// variable globale qui est une chaîne de caractères terminée par zéro.

struct Truc
{
   PPOINT m_lpptPosition;// variable membre d'une structure, pointeur sur une structure de type point.
};

INT cbExtra;// nombre(le 'c') d'octets(le 'b') supplémentaires (à allouer par exemple)

Évidemment, il ne faut pas en abuser. Dans l'exemple précédent, cela alourdit inutilement l'expression ; on pourra omettre le "pt".

Rédacteur : Pamaury

Quelques codes...

Depuis la parution de mon tutoriel, j'ai répondu à diverses questions, parfois en fournissant un code fonctionnel. J'ai donc décidé de répertorier ici-même ces différents codes afin que vous puissiez en profiter ;)

Gestion du presse-papier

Le presse-papier revient souvent sur les forums, en particulier en ce qui concerne les futurs traitements de texte en WinAPI. C'est pour cela que j'ai créé deux petites fonctions toutes simples permettant sa gestion, GetClipboardText() et SetClipboardText(), qui conviendront aux moins exigeants, tout en inspirant les plus exigeants :) :

#include <windows.h>

/* goto peut être utilisé si la redirection est dans le sens direct de la lecture du code (on évite le code spaguetti), et que son utilisation est claire... Pour moi, ça l'est. */
#define REDIRECT_IF(condition, label) if(condition) goto label

#define EXIT_LABEL local_exit
#define EXIT_IF(condition) REDIRECT_IF(condition, EXIT_LABEL)

/* Quand "on colle" : */
char *GetClipboardText()
{
    char *text = NULL;

    EXIT_IF(!IsClipboardFormatAvailable(CF_TEXT));

    EXIT_IF(!OpenClipboard(NULL));

    {
        char *textMem = (char*)GetClipboardData(CF_TEXT);

        /* free devra être utilisé une fois le texte utilisé */
        text = malloc((lstrlen(textMem)+1) * sizeof *text);
        lstrcpy(text, textMem);
    }

    CloseClipboard();

    EXIT_LABEL:
        return text;
}

/* Quand "on copie" : */
int SetClipboardText(char const *text)
{
    int ret = 0;

    HANDLE hGlobalMem = GlobalAlloc(GHND, lstrlen(text)+1);
    char *lpGlobalMem = NULL;

    EXIT_IF(hGlobalMem == NULL);

    lpGlobalMem = (char*)GlobalLock(hGlobalMem);

    EXIT_IF(lpGlobalMem == NULL);

    lstrcpy(lpGlobalMem, text);

    GlobalUnlock(hGlobalMem);

    EXIT_IF(!OpenClipboard(NULL));

    EmptyClipboard();

    if(SetClipboardData(CF_TEXT, hGlobalMem) != NULL)
        ret = 1;

    CloseClipboard();

    EXIT_LABEL:
        return ret; /* 1 : Succeed, 0 : Failure */
}

En ce qui concerne la détection du copier-coller, mieux vaut se fier à des fonctions dédiées à ces opérations, en spécifiant sa fenêtre listener des opérations de copier-coller, grâce à SetClipboardViewer(). Ainsi, toute modification du presse papier se traduira par la réception du message WM_DRAWCLIPBOARD, qu'il faudra traiter.

Gestion du drag and drop

La question du drag-and-drop est aussi quelque peu récalcitrante, puisque l'API Windows ne fournit pas de système direct pour s'en charger. J'en ai jadis proposé un, qui se résume à une unique fonction GetDroppedFilesPaths() :

Citation : Moi

Un petit exemple censé écrire les chemins des fichiers lâchés sur la fenêtre dans un fichier (situé dans le même répertoire que l'exécutable) nommé "Result.txt" (pas compilable tel quel).

/* Callback procedure : Soit hwnd (HWND) et wParam (WPARAM), passés en paramètre à la procédure. */
case WM_CREATE:
    /* Ne pas oublier d'accepter les drag-and-drop ;) */
    DragAcceptFiles(hwnd, TRUE);
    return 0;

case WM_DROPFILES:
{
    CHAR **paths = NULL;

    UINT count = GetDroppedFilesPaths((HDROP)wParam, paths);

    WritePathsToFile(paths, count, "Result.txt");

    /* On n'oublie pas de libérer la mémoire allouée */
    {
        UINT i = 0;
        while(i < count)
            free(paths[i++]);

        free(paths);
    }

    /* Ne pas oublier de finir la procédure de drag */
    DragFinish((HDROP)wParam);

    return 0;
}

Avec :

/* Il aurait mieux fallu utiliser le système de gestion des fichiers de l'API, mais bon... */
#include <stdio.h>
#include <windows.h>

VOID WritePathsToFile(
                          CONST CHAR ** CONST paths,
                          CONST UINT count,
                          LPCSTR resPath
                      )
{
    FILE *file = NULL;
    file = fopen(resPath, "a");

    if(file != NULL)
    {
        for(UINT i = 0 ; i < count ; i++)
            fputs(paths[i], file);

        fclose(file);
    }
}

static UINT GetDroppedFilesCount(HDROP hDrop)
{
    return DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
}

static UINT GetDroppedFileLen(HDROP hDrop, CONST UINT index)
{
    return DragQueryFile(hDrop, index, NULL, 0);
}

UINT GetDroppedFilesPaths(HDROP hDrop, CHAR **paths)
{
    const UINT count = GetDroppedFilesCount(hDrop);

    paths = malloc(count * sizeof *paths);
    if(paths == NULL)
        return 0;

    for(UINT i = 0 ; i < count ; i++)
    {
        const UINT droppedFilePathLen = GetDroppedFileLen(hDrop, i);

        paths[i] = malloc(droppedFileLen * sizeof *paths[i]);
        if(paths[i] == NULL)
            return i;

        DragQueryFile(hDrop, i, paths[i], droppedFileLen);
    }
    return count;
}

Il faut bien évidemment autoriser la fenêtre au drag-and-drop via les styles de la fenêtre ou DragAcceptFiles()...

N'oubliez pas de libérer la mémoire allouée par les chemins des fichiers récupérés ! Gare aux fuites de mémoire dans le cas contraire...

Gestion d'une icône dans la barre des tâches

Voici deux fonctions toutes simples permettant de minimiser et de restaurer une application :

/* Cache ton application et place une icône dans la barre de tâches,
dont le handle est hIcon, szTip le message affiché dans une petite bulle (due au
survol prolongé de la souris), et msg le message callback, permettant de traiter
les événements */
NOTIFYICONDATA *Minimize(HWND handler, HICON hIcon, LPCTSTR szTip, UINT msg)
{
    NOTIFYICONDATA *nim = malloc(sizeof *nim);

    nid->cbSize = sizeof(NOTIFYICONDATA);
    nid->hWnd = handler;
    nid->uID = 0;
    nid->hIcon = hIcon;
    nid->uCallbackMessage = msg;
    nid->uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;

    strcpy(nid->szTip, szTip);

    Shell_NotifyIcon(NIM_ADD, nid);
    ShowWindow(handler, SW_HIDE);

    return nid;
}

void Restore(NOTIFYICONDATA *data)
{
    ShowWindow(data->hWnd, SW_SHOW);
    Shell_NotifyIcon(NIM_DELETE, data);
}

Et voilà un exemple d'utilisation :

/* Par exemple, la zone utilisateur étant située après WM_USER */
#define TI WM_USER+1

/* On minimise la fenêtre en récupérant une structure qui servira à la restaurer */
NOTIFYICONDATA *data = Minimize(hwnd, LoadIcon(hInstance, "Icon"), "Je suis une TrayIcon", TI);

/* ... */

/* On restaure la fenêtre */
Restore(data);
/* Et on n'oublie pas de libérer la mémoire... */
free(data);

/* ... */

/* Quant au switch du low-word du message : */
case TI:
    /* Traitement, par exemple avec TrackPopupMenuEx(), voir msdn */

Encore une fois, n'oubliez pas de libérer la mémoire occupée par l'instance de NOTIFYICONDATA nouvellement créée !

Une idée à fouiller lors de l'encapsulation

Un jour on m'a demandé si c'était possible de spécifier une méthode comme callback, et voici ce que je lui ai répondu :

Citation : Moi

Le fait est que... C'est impossible ^^ La callback doit être statique. Edit: Question de signature... Ca paraît logique avec la structure WNDCLASSEX

Tu dois donc contourner le problème. Voici ce que je te propose : Utilise SetWindowLongPtr() après création de ta fenêtre pour passer l'objet courant (obtenu à travers la structure CREATESTRUCT passée par le LPARAM de la callback statique) en tant que pointeur récupérable grâce à GetWindowLong() (par exemple, à l'emplacement GWLP_USERDATA) dans la callback statique qui se chargera alors de rediriger les événements perçus vers les callbacks virtuelles correspondantes.

Fouille cette idée, si tu as du courage :

class Wnd
{
    HWND m_handle;

    //...

    static LRESULT StaticProc(HWND, UINT, WPARAM, LPARAM);
    static LRESULT DefProc(HWND, UINT, WPARAM, LPARAM);

    //...

    virtual LRESULT virtualProc(UINT, WPARAM, LPARAM); //Pure ou pas

    //...
};

class MyWnd : public Wnd
{
    //...

    virtual LRESULT virtualProc(UINT, WPARAM, LPARAM); //Override
};

LRESULT Wnd::StaticProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    LRESULT ret = 0;

    //Si la fenetre est en creation, le pointeur n'a pas ete encore initialise
    if(uMsg == WM_NCCREATE)
    {
        /* SetWindowLongPtr() de 'this' contenu dans lParam grâce au dernier paramètre
           de CreateWindowEx() -> Voir CREATESTRUCT structure */
        SetObjectFromCreateStruct(hWnd, lParam); //A toi de la créer...
    }

    Wnd *pObj = reinterpret_cast<Wnd*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
    if(pObj != NULL)
        ret = pObj->virtualProc(uMsg, wParam, lParam);
    else
        ret = DefProc( hWnd, uMsg, wParam, lParam);

    return ret;
}

Cela reste primitif, mais c'est une solution à envisager lorsqu'on est nous-même Orienté Objet ( ^^ ) et que l'on est réticent à utiliser MFC. Cela permet en outre de bien contrôler les rouages d'une API ainsi simplifiée.

Utiliser les Dynamic Link Library

Ou DLL qui vous permettent de découper le code dans d'autres fichiers joints à l'exécutable, soit afin de l'alléger (on lie tout de même statiquement pour s'affranchir des contraintes de chargement dynamiques, tout se passe comme s'il s'agissait d'une bibliothèque statique [.a, .lib] à la génération, mais les bibliothèques sont stockées à part dans des DLL), soit liées dynamiquement (chargement de la DLL et de ses fonctions à l'exécution, dans le code, aucune opération de linker). Les DLL permettent une maintenance facilitée, pour par exemple des mises à jour sur des points particuliers, simplement en remplaçant uniquement des DLL, sans toucher au reste. Elles sont particulièrement utiles pour les plugin.

DavySharp974, que je remercie au passage, nous propose une bibliothèque qui regroupe le code de liaison dynamique des DLL dans 3 fonctions. On ne détaille pas la création desdites DLL et d'une manière générale leur fonctionnement, mais cela peut vous faciliter la vie. Et qui sait, cela sûrement complété.

Citation : DavySharp974

Dans cette partie, vous pourrez apprendre comment charger dynamiquement une DLL sous Windows :) Pour ce faire, il vous faut télécharger libDLL

Dézippez les fichiers et mettez-les dans les bons emplacements (excepté le fichier 'test_dll.dll' qui doit être dans le même répertoire que votre projet de test).
Au cas où le lien soit cassé, ou pour simple information, voilà le code de la bibliothèque :

sqdll.h

/*
    Library for DLL loading.
    by Davy Sharp

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef __SQ_EXT_DLL_H
#define __SQ_EXT_DLL_H

#if !defined _WIN32 && !defined __Win32__
    #error "libDLL was designed to be used uner Windows only !"
#endif

#ifdef __cplusplus
extern "C"
{
#endif

    typedef struct sq_dll_struct_t* sqdll_t;

    sqdll_t dll_load(const char* file_name);
    void dll_free(sqdll_t dll_ptr);

    void* dll_function(sqdll_t dll_ptr, const char* function_name);

#ifdef __cplusplus
}
#endif

#endif // __SQ_EXT_DLL_H

sqdll.c

#include <windows.h>

#include "sqdll.h"

struct sq_dll_struct_t
{
    HINSTANCE h_dll;
};

void* dll_function(sqdll_t dll_ptr, const char* function_name)
{
    if (dll_ptr != NULL)
    {
        return GetProcAddress(dll_ptr->h_dll, function_name);
    }
    else
    {
        return NULL;
    }
}

sqdll_t dll_load(const char* file_name)
{
    sqdll_t ptr = malloc(sizeof(*ptr));

    if (ptr != NULL)
    {
        ptr->h_dll = LoadLibrary(file_name);

        if (ptr->h_dll == NULL)
        {
            free(ptr);

            ptr = NULL;

            return ptr;
        }
    }

    return ptr;
}

void dll_free(sqdll_t dll_ptr)
{
    if (dll_ptr != NULL)
    {
        FreeLibrary(dll_ptr->h_dll);

        free(dll_ptr);
    }
}

Nos pouvons à présent commencer à coder notre programme de test.
Ecrivez un main vide, avec un 'return 0' à la fin.

int main(int argn, char** argv)
{
     return 0;
}

Dans le header 'libdll.h', libDLL définit trois fonctions, les trois seules dont vous aurez besoin pour utiliser une DLL.

la première fonction s'appelle sqdll_t* dll_load(const char* nomDuFichierDLL). Comme son nom l'indique (où pas :p ), elle sert à charger la DLL.

Nous avons donc:

int main(int argn, char** argv)
{
     sqdll_t maDLL = dll_load("test_dll.dll"); // Chargement de la dll 'test_dll.dll' qui devrait être dans le répertoire de votre projet

     return 0;
}

Ensuite, la fonction la plus importante, celle qui permet d'utiliser les fonctions de la DLL, s'appelle void* dll_function(sqdll_t*, const char* nomDeLaFonction)

Elle prend en premier paramètre le pointeur de Handle de DLL et en second paramètre le nom de la fonction à charger.
Son retour est de type void* car elle ne sait pas le type de retour et celui des arguments de la fonction qu'on charge.

En revanche, nous, on le connait :) la DLL 'test_dll.dll' contient une fonction int ajouter(int a, int b)

Nous déclarons donc le pointeur sur la fonction ajouter comme ceci: int (*ajouter)(int, int)

Je récapitule, finalement, le code écrit est

int main(int argn, char** argv)
{
     sqdll_t maDLL = NULL; // On initialise à NULL
     int (*ajouter)(int, int) = NULL; // On initialise à NULL

     maDLL =  dll_load("test_dll.dll"); // Chargement de la dll 'test_dll.dll' qui devrait être dans le répertoire de votre projet

     if (!maDLL) // Si le chargement a échoué...
     {
         printf("Erreur, impossible de trouver 'test_dll.dll' !"); // On affiche un message d'erreur...

         return -1; // Et on renvoie un code d'erreur
     }

     ajouter = dll_function(maDLL, "ajouter"); // On charge la fonction 'ajouter' depuis la DLL

     if (!ajouter) // Si le chargement a échoué...
     {
          printf("Erreur, impossible de trouver la fonction 'ajouter' dans 'test_dll.dll'); // On affiche un message...

          dll_free(maDLL); // Ne pas oublier de libérer la DLL, même si la fonction n'a pas été trouvée dedans

          return -2; // Et on renvoie un code d'erreur
     }

     // Si on arrive jusqu'ici, c'est que tout s'est bien passé

     printf("La somme de %d et %d est %d", 429, 763, ajouter(429, 763)); // On utilise la fonction 'ajouter'

     dll_free(maDLL); // on n'oublie pas de libérer la DLL en quittant

     return 0;
}

Il me semblait en avoir d'autres en réserve, mais je vous avoue que j'ai un peu la flegme de me plonger dans mon courrier dans une chasse aux codes :-°
D'autres surgiront peut-être ?

Rédacteur : Mg++

Les annexes ne sont pas finies, elles sont avancées au fur et à mesure de la progression du tuto ;)

Manipulation du registre Programmes
Annexes
  1. Documentation
  2. Glossaire
  3. Manipulation du registre
  4. Divers

L'auteur

pasquestion etpuisquoiencore


Cours sur L'API Winodos de openclassrooms

Programmes

Ici seront recensés les programmes créés en WinAPI, dont vous m'aurez fait part.

Neoblast, de Kidpaddle2 (ex-Mg++)

Bonjour !

Je poste ici pour vous présenter un programme que j'ai jadis créé : NeoBlast.

Qu'est ce que c'est ?

NeoBlast (en français : "Souffle Nouveau") est un lecteur audio avancé pouvant lire la totalité des fichiers audios présents sur vos ordinateurs respectifs :

  • Wave

  • mp3

  • Wma

  • Ogg

  • etc...

Comment ?

J'ai utilisé la bibliothèque fmod afin d'avoir le plus de fonctions possible permettant de manipuler les fichiers audio pour les implémenter dans mon programme.

[Malheureusement, même si celle-ci est portable, la bibliothèque graphique utilisée (GUI) étant le windows API ou API win32 n'est pas portable, et ne fonctionnera que sous Windows.]

Que fait-il de spécial ?

NeoBlast ne fait rien de vraiment "spécial", mais se contente d'avoir la plupart des fonctionnalités des lecteurs audio "pro" présents sur le net :

  • lecture des fichiers audio (logique)

  • gestion de playlist

  • Drag and drop dans la playlist

  • Seeker

  • affichage en temps réel des infos de la musique (temps, titre, etc.)

  • fonctions principales (Play / Pause / Stop / Next / Previous)

  • ouverture simple des fichiers musicaux en unic ou multi select à travers une boite de dialogue d'ouverture

  • chargement / Sauvegarde de playlist au format m3P (format propriétaire)

  • scan du / des disques pour trouver automatiquement les fichiers audio

  • possibilité de rejouer la playlist arrivée à la fin

  • Shuffle

  • possibilité de lancer NeoBlast au démarrage :

  • minimisé

  • avec une playlist spécifiée

  • Skin améliorés

Parlons-en des skin... ils sont comment ?

Pour vous faire une idée, voici :

Le skin principal :

Le skin de la playlist :

Un grand merci à Musethebest pour la conception des skin... d'ailleurs un peu de pub :p .

[Le noir est utilisé pour la présentation : l'utilisation des régions m'a permis de pouvoir avoir des fenêtres ayant la forme de ces skins (avec la couleur noire comme transparente) sans barre de titre.]

Pour bouger les fenêtres modélisées par les skins, exécutez un drag avec le bouton droit de la souris. (voir "Help" pour quelques précisions).

Génial ! Où le télécharger ?

(Bin quoi, c'est possible non ? :-° )

Vous pourrez le télécharger (en étant équipé d'un extracteur de rar / zip) à partir des liens ci-dessous :
(N'oubliez pas de lire le fichier "Help" situé dans le menu Démarrer, et s'il vous plaît, ne sautez pas la licence.)

Liens :

  • Télécharger NeoBlast v1.0 (Anglais

  • Télécharger NeoBlast v1.0 (Français)

Place aux tests

Voilà la présentation est terminée, place aux tests !

Vous pourrez consulter les codes sources (C) présents dans le répertoire d'install de NeoBlast ; attention, ils sont très indigestes, je l'ai créé à mes débuts, n'étant à cette époque pas rigoureux.

Si vous avez des avis, des commentaires, des critiques [constructives] ou encore des suggestions, n'hésitez pas à écrire dans mon livre d'or situé sur mon site e-Synapse, où une présentation dudit logiciel est aussi présente (au cas où).

Le prochain programme à paraître est PlayTyle, nettement pus évolué et codé à l'aide d'MFC. Sa sortie n'est toutefois pas prévue pour demain, même s'il est bien avancé :lol:

Remerciements :

  • Musethebest qui s'est occupé des skins

  • Médinoc qui m'a beaucoup aidé sur le forum de developpez.com

___________________________________|

|_________________________________

DdPop, de damien.09

Qu'est ce que c'est ?

En quoi cela consiste t'il : (principales caractéristiques)

  • Programme permettant de s'échanger des messages sur un réseau local comme sur internet. (avec l'adresse IP ou le nom du PC distant)

  • Possibilité de s'échanger des fichiers (depuis la version 3.0 du 14/04/07)

  • Mettre en contact jusqu'a 8 personnes. (il faut pour cela le module DDpop-Serveur : vous pourez virer les utilisateurs qui sont pas sages :p )

  • Faire en sorte que le programme démarre et se connecte automatiquement à l'ouverture d'une session.

  • Possibilité de minimiser l'application dans la systray (=barre d'icône à coté de l'horloge).

  • Choix du port utilisé.

  • Choix de la police de caractère ainsi que des couleurs (couleur de fond et couleur du texte).

  • Vous pouvez aussi envoyer des Wizz (comme sur MSN).

  • Quelques sons pour agrémenter le tout.

Points n'ayant pu être développés :

  • Le multiplateforme : il marche que sous Windows :o (je me suis tout de même efforcé d'assurer la compatibilité avec Windows98 :) ).

Un aperçu

Lien et contact

Voici l'adresse ou vous pouvez télécharger DDpop : http://damien09.programmes.site.voila.fr/index.html. Il est conseillé de télécharger également le module DDpop-Serveur (ça va avec DDpop) comme ça vous pourrez être 8 à dialoguer ;) .
Les téléchargements (DDpop et DDpopServeur) incluent une Documentation expliquant le fonctionnement du programme correspondant, n'hésitez pas à la consulter.

Infos

Les caractéristiques indiquées ci-dessus ne sont peut être pas celles de la dernière version, si vous voulez les caractéristiques de la version actuelle référez vous à cette page.
DDpop et DDpop-Serveur sont des programmes codés en C++ avec l'API Windows par Dando.D.

N'hésitez pas à laisser des commentaires ici ou à me poser des questions :) et prevenez moi si vous trouvez des bugs.

La liste n'est pas complète, n'hésitez pas à rajouter les votres !

Sauf mention contraire, tous les projets sont sous licence GPL, et peuvent donc être étudiés (pas d'appropriation, et les modifications entraîneront obligatoirement une citation du créateur originel).

Pour ajouter votre projet à cette liste, créez un chapitre (mini-tuto), rédigez votre présentation avec une dernière sous-partie étant "Liens", puis copiez-collez vos rédactions (en zCode) dans un MP que vous m'enverrez ; je m'occuperais de la mise à jour.

Si vous avez des problèmes, contactez nous par MP.

Pour en savoir plus : Image utilisateur

Divers
Projets
  1. Programmes

L'auteur

pasquestion etpuisquoiencore


C++ Win32 Framework for Beginners

Win32++ Overview http://win32-framework.sourceforge.net

Download https://sourceforge.net/projects/win32-framework/files

Home
Overview
How it Works
Downloads
Tutorials

Examples
Why not MFC
Links
Contact

Introduction

Win32++ consists of a C++ library used to create window applications. It is a free alternative to MFC, and can be used on a larger range of compilers including those from Borland, Microsoft and MinGW. The library can be used to create a variety of programs, including simple windows, dialogs, property sheets, as well as frame and MDI frame applications. Information on how to use these classes to build your own Win32 applications is available in the Tutorial section.

Frame based applications usually have an outer frame window which looks after the menu, toolbar and status bar, and a separate window (sometimes referred to as a view window) over the client area. Many of the windows applications you are familiar with are likely to be frame based applications. You can use this library to produce not just simple windows and dialogs, but also professional looking frame based applications, complete with a menu, toolbar, status bar, and tooltips.

About the Downloads

DownLoad https://sourceforge.net/projects/win32-framework/files

To download a copy of Win32++ proceed to the Download section of the projects listing at SourceForge. The Downloads section provides a number of sample applications which use Win32++, including:

  • Browser
  • CustomControl
  • Dialog
  • DialogBars
  • DialogDemo
  • DialogResizing
  • DialogTab
  • DirectX
  • DLL
  • Dock
  • DockContainer
  • DockTabbedMDI
  • Explorer
  • FastGDI
  • FormDemo
  • Frame
  • GDIPlus
  • MDIFrame
  • MDIFrameDemo
  • MDIFrameSplitter
  • Networking
  • Notepad
  • Performance
  • Picture
  • PropertySheet
  • RibbonFrame
  • RibbonSimple
  • Scribble
  • Simple Window
  • Splitter
  • Tab Demo
  • TaskDialog
  • Themes
  • Threads Demo
  • ToolBar Demo
  • Tray Demo
  • WinCE Samples

Each of these examples include a "Readme.txt" which provides additional information about the example, and the features it demonstrates.

This project is also hosted on Code Project here.

Features

Win32++ provides a library for developing applications, using the Windows API directly. It supports all MS operating systems which run 32bit and 64bit Windows, from Windows 95 through to Windows XP and Vista. Win32++ is designed to produce programs with a similar look and feel to those created using MFC. This library can develop applications based on simple windows, dialogs, frames and MDI frames. The frames produced by Win32++ have the following features:

  • Rebar control
  • Menubar (a menu housed inside the rebar control).
  • Toolbar
  • Status bar
  • Tool tips

Win32++ also brings an object oriented approach to programming directly with the Windows API. Each window created is a C++ class object capable of having its own window procedure for routing messages.

A summary of the features of Win32++ are as follows:

  • Support for both Win32 and Windows CE operating systems. It supports all Win32 operating systems, from Windows 95 through to Vista. The Windows CE operating systems supported range from WCE400 (Windows mobile 2003) to WCE600 (Windows mobile 6).
  • Supports a wide range of C++ compilers, including those from Borland and Microsoft, as well as the GNU compiler used in Dev-C++.
  • Supports applications based on simple windows, dialogs, forms, property sheets, SDI frames and MDI frames. The SDI and MDI frames include rebars, status bars, menu bars, toolbars and tooltips. The frames use a separate window, often referred to as a view window, for the frame's client area.
  • Support for docking, tabbed containers and TabbedMDIs.
  • Message routing. The messages for each window are routed to the WndProc function of the associated CWnd object. All messages are passed to the WndProc function, including all those associated with window creation.
  • Automatic Subclassing. When creating a window using a predefined class type, Win32++ automatically subclasses the window so that messages can be routed via WndProc. Predefined class types include all the common controls, such as the toolbar, status bar, tree-view etc.
  • Existing windows can be attached to CWnd objects. This is particularly useful for controls in dialogs. These can be attached to to CWnd objects with the AttachDlgItem function.
  • Notification reflection. When a notification (WM_NOTIFY message) is received from a child window, OnNotifyReflect is called for the CWnd object associated with the child window.
  • Message reflection. Older common controls pass messages other than WM_NOTIFY to notify the parent window of events. The OnMessageReflect is called to pass these messages back to the CWnd object associated with the child window.
  • Network support. Win32++ provides a CSocket class which is a thin wrapper for much of the Winsock SPI. This class also provides notification of network events.
  • Exceptions. When attempts to use the Windows API functions fail, an exception is thrown to notify the programmer of the problem. When the project is compiled in debug mode, an error messages is also displayed which includes the information from GetLastError. Exceptions are thrown but no error messages are displayed when the project is compiled in release mode.
  • Tracing. Trace output is sent to the Output pane of the IDE when the program is compiled with debugging enabled.
  • Multi-Thread support. Win32++ is thread safe, and windows can be created in separate threads.
  • 64 bit support. Win32++ can be used to create 64bit applications.
  • Multilingual support. SetResourceHandle can be used to load resources from a resource dll to provide support for different languages.
  • Support for the Ribbon Framework available on Windows 7.

Requirements for using the Win32++

In order to use Win32++, you will need the following:

  • A C++ compiler.
  • Some knowledge of C++, including how to use classes
  • Some knowledge of the Windows API

You should also have a copy of the Microsoft Platform Software Development Kit, as this includes the documentation for programming with the Windows API. You will find this, as well as a selection of free C+ compilers and Windows API tutorials on the links page.

Getting Started

The library itself can be downloaded here. Simply start with one of the example programs. The example also include the project files for Visual Studio 6, Visual Studio.Net 2003, Visual C++ 2005 Express Edition, Visual C++ 2008 Express Edition, Dev-C++ and CodeBlocks. If you're using one of these, the samples are pre-configured, and ready to compile.

If you're new to C++ programming and looking for a free compiler to use, I recommend either Dev-C++ or Microsoft's Visual C++ 2008 Express Edition. I found these the easiest free compilers to set up and use. If you wish to compile the Browser sample you will need a Microsoft compiler, and will also need to install and incorporate the Platform SDK. Information on how to do this is provided by Microsoft here. To compile the DirectX sample you will need to install the DirectX SDK.


C++ Win32 Framework for Beginners, Tutorial

Tutorials

Home
Overview
How it Works
Downloads
Tutorial s

Examples

Why not MFC
Links
Contact

Menu of tutorials

Tutorial 1: The Simplest Window
Tutorial 2: Using Classes and Inheritance
Tutorial 3: Using Messages to Create a Scribble Window
Tutorial 4: Repainting the Window
Tutorial 5: Wrapping a Frame around our Scribble Window
Tutorial 6: Customising Window Creation
Tutorial 7: Customising the Toolbar
Tutorial 8: Loading and Saving Files
Tutorial 9: Printing
Tutorial 10: Finishing Touches

This set of tutorials will demonstrate how to create a scribble application, capable of drawing on a window using a mouse. It starts with a simple window and ends with a frame based scribble application which supports the loading and saving saving its data to a file, and printing the contents of the window.

The application produced by completing the tutorial looks like this.

Choose from the set of links in the Menu of Tutorials to start.


C++ Win32 Framework for Beginners

Tutorials

Home
About
How it Works
Documentation
Tutorials

Examples

Why not MFC
Links
Contact

Menu of tutorials

Tutorial 1: The Simplest Window
Tutorial 2: Using Classes and Inheritance
Tutorial 3: Using Messages to Create a Scribble Window
Tutorial 4: Repainting the Window
Tutorial 5: Wrapping a Frame around our Scribble Window
Tutorial 6: Customising Window Creation
Tutorial 7: Customising the Toolbar
Tutorial 8: Loading and Saving Files
Tutorial 9: Printing
Tutorial 10: Finishing Touches

Tutorial 1: The Simplest Window

The following code uses Win32++ to create a window. This is all the code you need (in combination with Win32++) to create and display a simple window. Note that in order to add the Win32++ code to our program, we use an #include statement as shown below.

#include "../Win32++/Wincore.h"

INT WINAPI WinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
    //Start Win32++
    CWinApp MyApp;

    //Create a CWnd object
    CWnd MyWindow;

    //Create (and display) the window
    MyWindow.Create();

    //Run the application
    return MyApp.Run();
}

This program has four key steps:

  • Start Win32++.  We do this here by creating a
        CWinApp object called MyApp.

  • Create a CWnd object called MyWindow.

  • Create a default window by calling the Create function.

  • Start the message loop, by calling the Run function.

If you compile and run this program, you'll find that the application
      doesn't end when the window is closed.  This is behaviour is
      normal.  An illustration of how to use messages to control the
      windows behaviour (including closing the application) will be left until
      tutorial 3.


C++ Win32 Framework for Beginners

Tutorials

Home
About
How it Works
Documentation
Tutorials

Examples

Why not MFC
Links
Contact

Menu of tutorials

Tutorial 1: The Simplest Window
Tutorial 2: Using Classes and Inheritance
Tutorial 3: Using Messages to Create a Scribble Window
Tutorial 4: Repainting the Window
Tutorial 5: Wrapping a Frame around our Scribble Window
Tutorial 6: Customising Window Creation
Tutorial 7: Customising the Toolbar
Tutorial 8: Loading and Saving Files
Tutorial 9: Printing
Tutorial 10: Finishing Touches

Tutorial 2: Using Classes and Inheritance

The program in the previous tutorial calls the CWinApp and CWnd classes directly. Normally, however, we would inherit from these classes to have more control over the type of CWnd and CWinApp objects we create.

This is an example of how we would derive a class from CWinApp.

class CSimpleApp : public CWinApp
{
public:
    CSimpleApp() {}
    virtual ~CSimpleApp() {}
    virtual BOOL InitInstance();

private:
    CView m_View;
};

BOOL CSimpleApp::InitInstance
{
    //Create the Window
    m_View.Create();

    return TRUE;
}

Notice that we override InitInstance to determine what happens when the application is started. In this instance we create the window for the m_View member variable. The m_View member variable is a CView object inherited from CWnd. The code for CView is shown below.

#include "../Win32++/WinCore.h"

class CView : public CWnd
{
public:
    CView() {}
    virtual ~CView() {}
};

Once again, if you compile and run this program you'll find that it will continue to run after the window is closed. We will demonstrate how to handle the message to close the application in the next section.


C++ Win32 Framework for Beginners

Tutorials

Home
About
How it Works
Documentation
Tutorials

Examples

Why not MFC
Links
Contact

Menu of tutorials

Tutorial 1: The Simplest Window
Tutorial 2: Using Classes and Inheritance
Tutorial 3: Using Messages to Create a Scribble Window
Tutorial 4: Repainting the Window
Tutorial 5: Wrapping a Frame around our Scribble Window
Tutorial 6: Customising Window Creation
Tutorial 7: Customising the Toolbar
Tutorial 8: Loading and Saving Files
Tutorial 9: Printing
Tutorial 10: Finishing Touches

Tutorial 3: Using Messages to create a Scribble Application

Each external event that a window might need to respond to is send to the window by way of a message. Its now time to control the way our window behaves by handling some of these messages. Each CWnd object handles its own messages in the WndProc function. In this example we will create a simple scribble program by handling the left mouse button messages. We will also handle the message to end the application when the window is closed.

The WM_LBUTTONDOWN message is sent to the window when the left button is clicked, and the cursor is over the window's client area. We capture the mouse input and store the current mouse position in the m_OldPt member variable.

The WM_MOUSEMOVE message is posted to a window when the cursor moves. If the mouse is not captured, the message is posted to the window that contains the cursor. Otherwise, the message is posted to the window that has captured the mouse. We check that the left button is also down, and call DrawLine to draw the line in the view window.

The WM_LBUTTTONUP message is sent to the window when the left mouse button changed from down to up during mouse capture.

The WM_DESTROY message is sent when a window is being destroyed.

A window receives messages through its window procedure. We intercept these messages and take our own actions by overriding the WndProc function.

LRESULT CView::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch (uMsg)
  {
  case WM_LBUTTONDOWN:
    OnLButtonDown(lParam);
    break;

  case WM_MOUSEMOVE:
    OnMouseMove(wParam, lParam);
    break;

  case WM_LBUTTONUP:
    OnLButtonUp(lParam);
    break;

  case WM_DESTROY:
    //End the program when window is destroyed
    ::PostQuitMessage(0);
    break;
  }

  //Use the default message handling for remaining messages
  return WndProcDefault(hWnd, uMsg, wParam, lParam);
}

Here are the definitions of the functions used in WndProc. SetCapture captures mouse input either when the mouse is over the capturing window, or when the mouse button was pressed while the mouse was over the capturing window and the button is still down.

void CView::OnLButtonDown(LPARAM lParam)
{
  // Capture mouse input.
  ::SetCapture(m_hWnd);

  m_OldPt.x = GET_X_LPARAM(lParam);
  m_OldPt.y = GET_Y_LPARAM(lParam);
}

void CView::OnLButtonUp(LPARAM lParam)
{
  {
    //Release the capture on the mouse
    ::ReleaseCapture();
  }
}

void CView::OnMouseMove(WPARAM wParam, LPARAM lParam)
{
  // hold down the left mouse button and move mouse to draw lines.
  if (wParam & MK_LBUTTON)
  {
    DrawLine(LOWORD(lParam), HIWORD(lParam));
    m_OldPt.x = GET_X_LPARAM(lParam);
    m_OldPt.y = GET_Y_LPARAM(lParam);
  }
}

This time the application ends when the window is closed. We detected when the window was closed by handling the WM_DESTROY message, and used this opportunity to end the application with the PostQuitMessage function.


C++ Win32 Framework for Beginners

Tutorials

Home
About
How it Works
Documentation
Tutorials

Examples

Why not MFC
Links
Contact

Menu of tutorials

Tutorial 1: The Simplest Window
Tutorial 2: Using Classes and Inheritance
Tutorial 3: Using Messages to Create a Scribble Window
Tutorial 4: Repainting the Window
Tutorial 5: Wrapping a Frame around our Scribble Window
Tutorial 6: Customising Window Creation
Tutorial 7: Customising the Toolbar
Tutorial 8: Loading and Saving Files
Tutorial 9: Printing
Tutorial 10: Finishing Touches

Tutorial 4: Repainting the Window

The previous example fails to repaint the window. Repainting the window is required whenever the window is resized, or when part of the window is revealed after being covered by another window.

The Windows API handles repainting automatically. When part of a window needs repainting, windows sends a WM_PAINT message to the application. Typically you would respond to this message with the code to redraw the entire client area of the window. You don't need to concern yourself with which parts of the client area need to be redrawn, as windows handles this part for you automatically.

Win32++ already contains the code to handle the WM_PAINT message in CWnd::WndProc. All you need to do is write the OnPaint function. For our application here, we can store the various points in a vector, and have the OnPaint function draw the lines again. The function to store the points looks like this.

void CView::StorePoint(int x, int y, bool PenDown)
{
  PlotPoint P1;
  P1.x = x;
  P1.y = y;
  P1.PenDown = PenDown;

  m_points.push_back(P1); //Add the point to the vector
}

Our OnPaint function looks like this.

void CView::OnPaint(CDC& dc)
{
  if (m_points.size() > 0)
  {
    bool bDraw = false;  //Start with the pen up

    for (unsigned int i = 0 ; i < m_points.size(); i++)
    {
      if (bDraw) dc.LineTo(m_points[i].x, m_points[i].y);
      else dc.MoveTo(hDC, m_points[i].x, m_points[i].y);
      bDraw = m_points[i].PenDown;
    }
  }
}


C++ Win32 Framework for Beginners

Tutorials

Home
About
How it Works
Documentation
Tutorials

Examples

Why not MFC
Links
Contact

Menu of tutorials

Tutorial 1: The Simplest Window
Tutorial 2: Using Classes and Inheritance
Tutorial 3: Using Messages to Create a Scribble Window
Tutorial 4: Repainting the Window
Tutorial 5: Wrapping a Frame around our Scribble Window
Tutorial 6: Customising Window Creation
Tutorial 7: Customising the Toolbar
Tutorial 8: Loading and Saving Files
Tutorial 9: Printing
Tutorial 10: Finishing Touches

Tutorial 5: Wrapping a Frame around our Scribble Application

This is the code to produce a simple frame application. We inherit a class called CMainframe from the CFrame class in Win32++. Notice how we use the SetView function to specify the client window of our frame. This client view happens to be the same code as the simple scribble application shown in the previous tutorial. Hence we have wrapped a frame around our previous scribble application.

The CMainFrame class inherits OnCommand from CFrame. This function responds to input from the frame's menu and toolbar. We haven't implemented many of these inputs yet, so at this stage most of them simply display a simple message box.

CMainFrame::CMainFrame()
{
  //Set m_View as the view window of the frame
  SetView(m_View);
}

BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam)
{
  // Process the messages from the Menu and Tool Bar
  switch (LOWORD(wParam))
  {
  case IDM_FILE_EXIT:
    // End the application
    ::PostMessage(m_hWnd, WM_CLOSE, 0, 0);
    return TRUE;
  case IDM_HELP_ABOUT:
    // Display the help dialog
    OnHelp();
    return TRUE;
  case IDM_FILE_NEW:
    ::MessageBox(NULL, _T("File New  ... Implemented later"), _T("Menu"), MB_OK);
    return TRUE;
  case IDM_FILE_OPEN:
    ::MessageBox(NULL, _T("File Open  ... Implemented later"), _T("Menu"), MB_OK);
    return TRUE;
  case IDM_FILE_SAVE:
    ::MessageBox(NULL, _T("File Save  ... Implemented later"), _T("Menu"), MB_OK);
    return TRUE;
  case IDM_FILE_SAVEAS:
    ::MessageBox(NULL, _T("File SaveAs  ... Implemented later"), _T("Menu"), MB_OK);
    return TRUE;
  case IDM_FILE_PRINT:
    ::MessageBox(NULL, _T("File Print  ... Implemented later"), _T("Menu"), MB_OK);
    return TRUE;
  }

  return FALSE;
}


C++ Win32 Framework for Beginners

Tutorials

Home
About
How it Works
Documentation
Tutorials

Examples

Why not MFC
Links
Contact

Menu of tutorials

Tutorial 1: The Simplest Window
Tutorial 2: Using Classes and Inheritance
Tutorial 3: Using Messages to Create a Scribble Window
Tutorial 4: Repainting the Window
Tutorial 5: Wrapping a Frame around our Scribble Window
Tutorial 6: Customising Window Creation
Tutorial 7: Customising the Toolbar
Tutorial 8: Loading and Saving Files
Tutorial 9: Printing
Tutorial 10: Finishing Touches

Tutorial 6: Customising Window Creation

Up until now we have used the default parameters supplied by Win32++ to create the view window. Here we use the PreRegisterClass to specify the Window Class (not to be confused with a C++ class) parameters prior to window creation. This will allow us to create a window with a coloured background and set it's cursor. We also use PreCreate to give the window a 3D look by giving it's border a sunken edge.

void CView::PreCreate(CREATESTRUCT &cs)
{
  // Set the extra style to provide a sunken edge
  cs.dwExStyle = WS_EX_CLIENTEDGE;
}
void CView::PreRegisterClass(WNDCLASS &wc)
{
  // Set the background brush and cursor
  wc.hbrBackground = m_hBrush;
  wc.lpszClassName = "Scribble Window";
  wc.hCursor = ::LoadCursor(GetApp()->GetInstanceHandle(), MAKEINTRESOURCE(IDC_CURSOR1));
}

Notice that we used GetApp()->GetInstanceHandle() in the LoadCursor function to get the instance handle (or HINSTANCE) of our application. The GetApp function returns a pointer to our CScribbleApp object, and GetInstanceHandle returns the instance handle of our application.

We create the background brush in CView's constructor, and delete it in CView's destructor. Brushes are a Windows resource. Any brush we create must be deleted to avoid causing a resource leak. This is how its done.

// Constructor
CView::CView() : m_PenColor(RGB(0,0,0))
{
     m_hBrush = ::CreateSolidBrush(RGB(255,255,230));
}

// Destructor
CView::~CView()
{
    ::DeleteObject(m_hBrush);
}


C++ Win32 Framework for Beginners

Tutorials

Home
About
How it Works
Documentation
Tutorials

Examples

Why not MFC
Links
Contact

Menu of tutorials

Tutorial 1: The Simplest Window
Tutorial 2: Using Classes and Inheritance
Tutorial 3: Using Messages to Create a Scribble Window
Tutorial 4: Repainting the Window
Tutorial 5: Wrapping a Frame around our Scribble Window
Tutorial 6: Customising Window Creation
Tutorial 7: Customising the Toolbar
Tutorial 8: Loading and Saving Files
Tutorial 9: Printing
Tutorial 10: Finishing Touches

Tutorial 7: Customising the Toolbar

Our frame window has both a menu and a toolbar at the top. Customising the menu is relatively straight forward, since we can use a resource editor to perform that task. Customising the toolbar however is another matter. While its true that the resource editor which ships Microsoft's Visual Studio can edit the toolbar resource, this is not standard, and we so need to come up with a standards compliant way of modifying the toolbar.

To set your own toolbar for your applications, you will need to perform the following steps:

  • Modify the toolbar bitmap.
  • Add the toolbar resource IDs to to the string table.
  • Assign resource IDs to the toolbar buttons, as shown below.
void CMainFrame::SetupToolbar()
{
    // Define the resource IDs for the toolbar
    AddToolbarButton( IDM_FILE_NEW   );
    AddToolbarButton( IDM_FILE_OPEN  );
    AddToolbarButton( IDM_FILE_SAVE  );
    AddToolbarButton( 0 );				// Separator
    AddToolbarButton( IDM_EDIT_CUT,   FALSE );
    AddToolbarButton( IDM_EDIT_COPY,  FALSE );
    AddToolbarButton( IDM_EDIT_PASTE, FALSE );
    AddToolbarButton( 0 );				// Separator
    AddToolbarButton( IDM_FILE_PRINT );
    AddToolbarButton( 0 );				// Separator
    AddToolbarButton( IDM_HELP_ABOUT );
}


C++ Win32 Framework for Beginners

Tutorials

Home
About
How it Works
Documentation
Tutorials

Examples

Why not MFC
Links
Contact

Menu of tutorials

Tutorial 1: The Simplest Window
Tutorial 2: Using Classes and Inheritance
Tutorial 3: Using Messages to Create a Scribble Window
Tutorial 4: Repainting the Window
Tutorial 5: Wrapping a Frame around our Scribble Window
Tutorial 6: Customising Window Creation
Tutorial 7: Customising the Toolbar
Tutorial 8: Loading and Saving Files
Tutorial 9: Printing
Tutorial 10: Finishing Touches

Tutorial 8: Loading and Saving Files

In this tutorial we will demonstrate how to save our data to a file, and load it back again.

Saving Data

To save the data we perform the following steps:

  • Use GetSaveFileName to open a dialog and choose the filename to save the data to.
  • Use CreateFile to open the file for writing.
  • Use WriteFile to write the data to the file.

This code segment shows how to assign values to an OPENFILENAME structure prior to calling GetSaveFileName to choose the file name of our data file.

// Fill the OPENFILENAME structure
TCHAR szFilters[] = _T("Scribble Files (*.dat)\0*.dat\0\0");
TCHAR szFilePathName[_MAX_PATH] = _T("");
OPENFILENAME ofn = {0};
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = m_hWnd;
ofn.lpstrFilter = szFilters;
ofn.lpstrFile = szFilePathName;  // This will hold the file name
ofn.lpstrDefExt = _T("dat");
ofn.nMaxFile = _MAX_PATH;
ofn.lpstrTitle = _T("Save File");
ofn.Flags = OFN_OVERWRITEPROMPT;

// Open the file save dialog, and choose the file name
GetSaveFileName(&ofn);
This next code segment demonstrates how to open a file for writing, and write data to it. The data written in this case is the contents of the m_points vector. Error checking has been omitted from the code segment displayed here for clarity, but is included in the code for this tutorial.
void CView::FileSave(LPCTSTR szFilename)
{
  DWORD nBytesWritten;
  HANDLE hFile = CreateFile(szFilename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
                      FILE_ATTRIBUTE_NORMAL, NULL);

  for (int i = 0; i < (int)m_points.size(); ++i)
  {
    WriteFile(hFile, &m_points[i], sizeof(PlotPoint), &nBytesWritten, NULL);
  }

  CloseHandle(hFile);
}

Loading Data

Reading data back from a file follows a rather similar process. The steps involved are as follows:

  • Use GetOpenFileName to open a dialog and choose the file to load.
  • Use CreateFile to open the file for reading.
  • Use ReadFile to retrieve the data from the file.

The following code segment demonstrates the use of GetOpenFileName.

// Fill the OPENFILENAME structure
TCHAR szFilters[] = _T("Scribble Files (*.dat)\0*.dat\0\0");
TCHAR szFilePathName[_MAX_PATH] = _T("");
OPENFILENAME ofn = {0};
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = m_hWnd;
ofn.lpstrFilter = szFilters;
ofn.lpstrFile = szFilePathName;  // This will the file name
ofn.nMaxFile = _MAX_PATH;
ofn.lpstrTitle = _T("Open File");
ofn.Flags = OFN_FILEMUSTEXIST;

// Bring up the dialog, and choose the file
::GetOpenFileName(&ofn)

This is the code which loads our PlotPoint vector from the contents of the chosen file. Once again, the error checking has been omitted from the code displayed here for clarity, but is present in the source code.

void CView::FileOpen(LPCTSTR szFilename)
{
// empty the PlotPoint vector
m_points.clear();

DWORD nBytesRead;

// Create a handle to the file
HANDLE hFile = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

  do
  {
    nBytesRead = 0;
    PlotPoint pp;

    // Read a PlotPoint struct from the file
    ReadFile(hFile, &pp, sizeof(PlotPoint), &nBytesRead, NULL);

    // store the PlotPoint in the PlotPoint vector
    m_points.push_back(pp);

  } while (nBytesRead == sizeof(PlotPoint));

  CloseHandle(hFile);

  // repaint the view window
  ::InvalidateRect(m_hWnd, NULL, TRUE);
}


C++ Win32 Framework for Beginners

Tutorials

Home
About
How it Works
Documentation
Tutorials

Examples

Why not MFC
Links
Contact

Menu of tutorials

Tutorial 1: The Simplest Window
Tutorial 2: Using Classes and Inheritance
Tutorial 3: Using Messages to Create a Scribble Window
Tutorial 4: Repainting the Window
Tutorial 5: Wrapping a Frame around our Scribble Window
Tutorial 6: Customising Window Creation
Tutorial 7: Customising the Toolbar
Tutorial 8: Loading and Saving Files
Tutorial 9: Printing
Tutorial 10: Finishing Touches

Tutorial 9: Printing

In this tutorial we will demonstrate how to send a bitmap to a printer. The bitmap in this case will be the one we've been drawing on in the view window. The resulting printout will be resized to match the size of the original drawing. This task can be broken down into several steps:

  • Extract the bitmap from the view window
  • Choose the printer
  • Start the print job
  • Extract the bitmap image data from the bitmap
  • Copy the resized image data to the printer's device context
  • End the print job

Extract the bitmap from the view window

When we draw on the view window, we are actually drawing on to a bitmap attached to view window's device context. Here we will copy this bitmap to a compatible bitmap.

// Extract the bitmap from the View window
CDC ViewDC = m_View.GetDC();
CDC MemDC = CreateCompatibleDC(ViewDC);
MemDC.CreateCompatibleBitmap(ViewDC, Width, Height);
BitBlt(MemDC, 0, 0, Width, Height, ViewDC, 0, 0, SRCCOPY);
HBITMAP hbmView = MemDC.DetachBitmap();

Choose a printer

This step is fairly straight forward. We declare a PRINTDLG struct and use this in the PrintDlg function. The PrintDlg function brings up a dialog, allowing us to choose the printer, and stores its device context in the PRINTDLG struct.

// Bring up a dialog to choose the printer
PRINTDLG pd = {0};
pd.lStructSize = sizeof( pd );
pd.Flags = PD_RETURNDC;

// Retrieve the printer DC
PrintDlg( &pd );

Start the Print Job

The StartDoc function should be called before sending output to a printer. This function ensures that multipage documents are not interspersed with other print jobs. StartPage (and a corresponding EndPage) is then called for each page of printout.

// Zero and then initialize the members of a DOCINFO structure.
DOCINFO di = {0};
di.cbSize = sizeof(DOCINFO);
di.lpszDocName = _T("Scribble Printout");
di.lpszOutput = (LPTSTR) NULL;
di.lpszDatatype = (LPTSTR) NULL;
di.fwType = 0;

// Begin a print job by calling the StartDoc function.
StartDoc(pd.hDC, &di);

// Inform the driver that the application is about to begin sending data.
StartPage(pd.hDC);

Extract the bitmap image data from the bitmap

In order to use StretchDIBits we first need the bitmap image data. This is retrieved by using the GetDIBits. It is called twice in the following code sample, the first time to get the size byte array to hold the data, and the second to fill the byte array.

// Get the dimensions of the View window
RECT rcView;
GetClientRect (m_View.GetHwnd(), &rcView);
int Width = rcView.right - rcView.left;
int Height = rcView.bottom - rcView.top;

// Fill the BITMAPINFOHEADER structure
BITMAPINFOHEADER bi = {0};
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biHeight = Height;
bi.biWidth = Width;
bi.biPlanes = 1;
bi.biBitCount =  24;
bi.biCompression = BI_RGB;

// Note: BITMAPINFO and BITMAPINFOHEADER are the same for 24 bit bitmaps
// Get the size of the image data
GetDIBits(MemDC, hbmView, 0, Height, NULL, (BITMAPINFO*)&bi, DIB_RGB_COLORS);

// Retrieve the image data
byte* pBits = new byte[bi.biSizeImage];
GetDIBits(MemDC, hbmView, 0, Height, pBits, (BITMAPINFO*)&bi, DIB_RGB_COLORS);

Copy the resized bitmap to the printer's device context

StretchDIBits is the function used here to copy the bitmap information to the printer's device context because the bitmap needs to be resized in order to retain the same dimensions on the printed page as the original. The following code segment shows how the scaling factors are calculated and the StretchDIBits function is called.

// Determine the scaling factors required retain the bitmap's original proportions.
float fLogPelsX1 = (float) GetDeviceCaps(ViewDC, LOGPIXELSX);
float fLogPelsY1 = (float) GetDeviceCaps(ViewDC, LOGPIXELSY);
float fLogPelsX2 = (float) GetDeviceCaps(pd.hDC, LOGPIXELSX);
float fLogPelsY2 = (float) GetDeviceCaps(pd.hDC, LOGPIXELSY);
float fScaleX = max(fLogPelsX1, fLogPelsX2) / min(fLogPelsX1, fLogPelsX2);
float fScaleY = max(fLogPelsY1, fLogPelsY2) / min(fLogPelsY1, fLogPelsY2);

// Compute the coordinates of the upper left corner of the centered bitmap.
int cWidthPels = GetDeviceCaps(pd.hDC, HORZRES);
int xLeft = ((cWidthPels / 2) - ((int) (((float) Width) * fScaleX)) / 2);
int cHeightPels = GetDeviceCaps(pd.hDC, VERTRES);
int yTop = ((cHeightPels / 2) - ((int) (((float) Height) * fScaleY)) / 2);

// Use StretchDIBits to scale the bitmap and maintain its original proportions
StretchDIBits(pd.hDC, xLeft, yTop, (int) ((float) Width * fScaleX),
               (int) ((float) Height * fScaleY), 0, 0, Width, Height, pBits,
               (BITMAPINFO*)&bi, DIB_RGB_COLORS, SRCCOPY);

End the print job

To finish the print job, EndPage is called to indicate that printing to this page is complete, then EndDoc is called the end the print job.

// Inform the driver that the page is finished.
EndPage(pd.hDC);

// Inform the driver that document has ended.
EndDoc(pd.hDC);

// Cleanup
::DeleteObject(hbmView);
delete []pBits;


C++ Win32 Framework for Beginners

Tutorials

Home
About
How it Works
Documentation
Tutorials

Examples

Why not MFC
Links
Contact

Menu of tutorials

Tutorial 1: The Simplest Window
Tutorial 2: Using Classes and Inheritance
Tutorial 3: Using Messages to Create a Scribble Window
Tutorial 4: Repainting the Window
Tutorial 5: Wrapping a Frame around our Scribble Window
Tutorial 6: Customising Window Creation
Tutorial 7: Customising the Toolbar
Tutorial 8: Loading and Saving Files
Tutorial 9: Printing
Tutorial 10: Finishing Touches

Tutorial 10: Finishing Touches

In this final tutorial we will show how to store the application's settings in the registry, and implement tracing.

Saving the Window Position

Users will expect modern applications to save their settings, such as the position and size of the frame window. These settings are stored in the registry. LoadRegistrySettings is used to set the name of the registry key. Typically the name takes the form of "CompanyName\\ApplicationName" as demonstrated below.

CMainFrame::CMainFrame()
{
  .
  .
  .

  // Set the registry key name, and load the initial window position
  // Use a registry key name like "CompanyName\\Application"
  LoadRegistrySettings(_T("Win32++\\Scribble Sample"));
}

If a registry key name has been set using the LoadRegistrySetting function, the registry settings will be loaded from the registry when the application starts, and stored in the registry when the application ends. Override the LoadRegistrySettings and SaveRegistrySettings functions in CMainFrame if you wish to store other settings in the registry.

Saving the Most Recently Used (MRU) List

Applications which load and store files typically allow the user to choose from a list of recently used file names to load from. This MRU list is also stored in the registry. To add this capability to your Win32++ application, use the LoadRegistryMRUSettings function to specify the number of entries in the MRU list (up to a maximum of 16) as shown below.

CMainFrame::CMainFrame()
{
  .
  .
  .

  // Set the registry key name, and load the initial window position
  // Use a registry key name like "CompanyName\\Application"
  LoadRegistrySettings(_T("Win32++\\Scribble Sample"));

  // Load the settings from the registry with 4 MRU entries
  LoadRegistryMRUSettings(4);
}

Debugging your Application with Tracing

One important debugging technique is to trace what is happening with the application while it is running. The tracing allows you to display messages or the contents of variables in the output pane of Visual Studio (or whichever Integrated Development Environment you use).

In order to take advantage of this, you will need do the following

  • Use Version 6.0 (or later) of Win32++
  • Run your application in debug mode (see below)
  • Use the TRACE function to display the messages.

To run the application in debug mode, you need to have the _DEBUG variable defined. Microsoft's Visual Studio products define this variable for you when you compile in debug mode. For other compilers you should use the appropriate compiler option to set this variable. The source code for this example, as well as the samples in the download section, include project files for debug modes for both DevC++ and CodeBlocks to make this task easier.

If you are not running in debug mode, TRACE statements have no effect. You can leave them in place for Release mode if you wish.

In the sample code for this section we add tracing to our scribble application to display the position of the mouse for the lines we draw.

This is the code added to CView::WndProc for tracing.

char str[80];
::wsprintf(str, "Draw Point: %hd, %hd\n", GET_X_LPARAM(lParam),
             GET_Y_LPARAM(lParam));
TRACE(str);

These are the lines of code added to CMainFrame::WndProc

TRACE("Red pen selected\n");
TRACE("Blue pen selected\n");
TRACE("Green pen selected\n");
TRACE("Black pen selected\n");

The source code for this tutorial is the Scribble sample.


Win32++

Welcome to the home of Win32++

Home
Overview
How it Works
Downloads
Tutorials

Examples

Why not MFC
Links
Contact

About Win32++

This documentation describes the features of Win32++, version 7.6. To download a copy of Win32++ proceed to the project's hosting at SourceForge here.

Win32++ is a C++ library used to build windows applications. Win32++ is a free alternative to MFC. It has the added advantage of being able to run on a wide range of free compilers, including Visual Studio Express, and the MinGW compiler provided with CodeBlocks and Dev-C++.

Win32++ provides a thin layer over the Windows API and is similar to MFC. There are several differences to MFC however. Win32++ uses very few macros, and doesn't use macros for message maps. All the code for Win32++ is provided in a set of header files. There is no need for any additional DLLs or libraries apart from those DLLs that are part of the window operating system. Win32++ has view windows but doesn't impose a Doc/View architecture on users. User are free to employ a a Doc/View architecture if they choose.

Win32++ supports all Windows operating systems, from Windows 95 through to Windows 8.1 and Windows Server 2012. It also supports the Windows CE operating system which runs on runs on the various Pocket PCs, Smartphones, as well as industrial devices and embedded systems.

Get Win32++ at SourceForge.net. Fast, secure and Free Open Source software downloads


C++ Win32 Framework for Beginners, Links

Some Useful Links

Home
Overview
How it Works
Downloads
Tutorials

Examples

Why not MFC
Links
Contact

Free Compilers and Tools

Dev-C++ | Dev-C++ is a Integrated Development Environment (IDE) for C/C++. It includes the MinGW port of GCC as it's compiler.
Code::Blocks | Code::Blocks is an IDE for C/C++. It includes the MinGW port of GCC, and also supports the MS Visual Toolkit, and the free command line compiler from Borland.
Microsoft Visual C++ 2010 Express | A free C++ Win32 compiler and IDE from Microsoft.
Borland C++ Builder | A free C++ Win32 compiler from Borland. You will need to register before downloading.
DJGPP | A free C++ compiler for DOS.
ResEdit | A free Resource Editor for Win32 programs.
GDIView | A utility that displays list a of GDI handles used by each process. It's useful for spotting GDI leaks.
Winspector | A free alternative to Spy++. It's utility that displays window information as well as window messages.

Tutorials and References

MSDN | The Microsoft Developers Network. Microsoft's online reference.
MS Platform SDK | The Microsoft Platform Software Development Kit contains reference information on the Windows API.
MS Windows SDK | Additional tools, code samples, documentation, compilers, headers, and libraries.
MS DirectX SDK | Contains the additional header files and libraries for DirectX programming.
Code Project | Forums and Code samples for software developers.
SourceForge | SourceForge hosts a large collection of open source projects.
Cfanatic | A forum for C, Win32, C++, MFC, C# developers.
Reliable Software | A Win32 tutorial for advanced C++ users.
the Forger | A Win32 tutorial for C++.
FunctionX | A Win32 programming reference, by FunctionX.
Dev-C++ Resource Site | API references, tutorials, online resources, documentation downloads, etc. about C/C++ and Win32 programming.
Thinking in C++ | Thinking in C++ 2nd Edition. An excellent book on C++ by Bruce Eckel. Available Online.


C++ Win32 Framework for Beginners, Explanation

Why Beginners Shouldn't use MFC

Home
Overview
How it Works
Downloads
Tutorials

Examples

Why not MFC
Links
Contact

Its my view that MFC is the best library for professional developers. Its comprehensive, well documented, and officially supported by Microsoft. WTL is also a very impressive body of work. Its a good alternative to MFC for the experienced programmer who wants to work more closely with the Windows API. Even so, I doubt that either of these platforms are an ideal choice for novice C++ programmers.

The reason I take this view is that I believe that both MFC and WTL are tailored more for the professional or experienced programmer than the beginner. Speaking from experience, they don't necessarily meet the requirements of someone looking to teach themselves to program windows in C++. The requirements that I believe a novice programmer is likely to have from a windows library are somewhat different from the experienced programmer. They include:

  • The library should make it easy to get started. To be fair, MFC attempts to help here by providing wizards to automate some of the programming, and macros to hide some of the complexity. For the experienced programmer these work well. The wizards speed the initial code development, and the macros keep the code tidy and easy to read. The problem for the novice programmer is that they end up with code in their source files they simply don't understand, and that's not a good thing.
  • The library itself should be simple and easy to understand. WTL fails this rather badly. Yes it's neat and brilliantly written, but that's actually its problem. The dependence on templates will put its actual workings beyond the comprehension any normal novice user. MFC also fails this test, simply because its so vast.
  • We shouldn't need to learn a lot of new things in order to use it. MFC in particular fails this test. It is a vast and comprehensive library, but this makes it all the more difficult for beginners to get started with it.
  • The library should compile on free compilers, not just Visual Studio. Visual Studio is not cheap, and novice programmers probably won't want to spend lots of money to experiment with C++ and learn to program. Some are probably still at school. Their first choice of a C++ compiler/IDE will probably be a free one like Dev-C++.
  • The library should be freely available and free of charge.

Whether we're a novice programmer or an experienced one, I believe its important that we understand each line of our source code. This applies both to the code we write by hand, and any code written for us by wizards. For me, the thing which separates computer programming apart from many other disciplines is that its a precise science. A well written program is one that (among other things) behaves precisely as the programmer intended.

The problem for beginners with libraries like WTL and MFC is that they are likely to end up with source files that includes lines of code that they don't actually understand. The convenience of wizards in MFC can actually make this problem worse. Novice coders are perhaps encouraged to think that they don't need to understand all the lines of code in their source, since a wizard will do the work for them. When this happens, programming becomes less of a precise science, and more of a black art. Worst of all, the failure to understand their own code is rather likely to hold the novice programmer back, and cause them to gloss over fundamental concepts, like message maps.

In my case, after using MFC for a while I realized that I didn't thoroughly understand the code I was writing. I wasn't happy with that so went back to working with the windows API directly. This library was the result. I imagine that others who teach themselves to program for windows using C++ would also face similar challenges, and I hope they find the library I developed useful.


C++ Win32 Framework for Beginners, Download

Examples

Home
Overview
How it Works
Downloads
Tutorials

Examples

Why not MFC
Links
Contact

Win32++ Examples

A number of sample programs are provided with Win32++. These samples demonstrate how Win32++ can be used in a variety of different ways.

Browser. This example demonstrates use the CWebBrowser class to provide a display an internet browser within a Win32++ frame. The browser doesn't require ATL or any Microsoft specific code.
Dialog. Use this as your starting point when creating your own dialog based applications.

DirectX. This program shows how to override the message loop to cater for DirectX.
Docking Examples. This collection of three examples demonstrate docking, container docking and TabbedMDIs.
Explorer. This program demonstrates some shell programming techniques. It looks and behaves much like windows explorer and supports context menus.
Frame. Use this as your starting point when creating your own frame based applications. The frame has a menu and toolbar (inside a rebar), and statusbar.

FormDemo. A form is a dialog within a frame. It allows the dialog to have a menu, toolbar and statusbar. Use this as your starting point when creating your own form based applications.

MDIFrame. Use this as your starting point when creating your own MDI frame based applications.

MDIFrameDemo. This program demonstrates some of the features MDI (Multiple Document Interface) frames. Features include creating maximized MDI child windows, and confirming before a MDI child is closed.
Scribble Sample. A basic pen drawing application. This is the program described in the tutorial section. It demonstrates fundamental message handling.
Simple Window. This example demonstrates the fundamentals of creating and customizing a simple window. Use this as your starting point when creating your own simple window applications.

Themes Sample. A program which demonstrates the various themes supported Win32++. Themes allow you to customise the rebar with a range of styles and a gradient filled background with your choice of colors. Themes also use custom draw techniques to customise the colors of the toolbar.
Threads. This program shows how to use CThread to create several windows, each in a separate thread.
WinCE Samples. This file group of samples contains three generic starters, plus several sample applications for Windows CE.


C++ Win32 Framework for Beginners, Description

How it Works

Home
Overview
How it Works
Downloads
Tutorials

Examples

Why not MFC
Links
Contact

Win32++ Classes

The following diagram illustrates the classes used in Win32++:

The classes which define the library itself are contained within the Win32xx namespace. These classes are as follows:

  • CCmdbar: A class used on Windows CE to provide a CommandBar. It is used by CFrame on Windows CE.
  • CCriticalSection: This class provides for thread synchronization for multi-threaded applications.
  • CDC: A class which represents a Device Context. It simplifies working the windows GDI.
  • CDialog: The class responsible for creating modal and modeless dialogs. It is used by CFrame, and can also be used to create dialog applications.
  • CFrame: This class produces a frame window which has a rebar, menubar, toolbar, and a status bar. The client area of the frame window should be occupied by a separate CWnd object.
  • CMDIApp:This class is inherited from CWinApp. You should inherit from this class to start a MDI frame application
  • CMDIChild: This is the class to be used for MDI children. Each MDI child should be inherited from this class.
  • CMDIClient:This is a class used internally by Win32++ as the view window for the MDI frame.
  • CMDIFrame:This class is responsible for creating the MDI frame window. It is inherited from CFrame.
  • CMenubar: This class is responsible for creating the menubar. A menubar is a menu housed inside a rebar control.
  • CPropertyPage: This class add support for property pages to Win32++. A property page has one or more property sheets.
  • CPropertySheet: This class represents a property page. It is used by CPropertySheet.
  • CRebar:This class is responsible for creating the rebar. It is used by CFrame.
  • CSocket: This class adds network support to Win32++.
  • CSplitter: This class can be used to provide a splitter window.
  • CStatusBar: The class responsible for creating the status bar. It is used by CFrame.
  • CToolBar: The class responsible for creating the tool bar. It is used by CFrame.
  • CWceFrame: A class which provides a simple frame for Pocket PCs. It utilises a Commandbar to display the menu and toolbar buttons..
  • CWinApp: The class responsible for initializing Win32++, and also provides our message loop. You should inherit from this class to start the application.
  • CWinException: A class which handles exceptions.
  • CWnd: The class responsible for the window objects. It is the base class for the more specialized window objects such as CDialog, CFrame, CToolbar etc.

Using Win32++

The code which forms the basis of Win32++ is located in the Win32++ directory. You shouldn't need to modify these files, but rather inherit from the Win32++ and add any additional code in your derived classes. To create a SDI frame window, for example, you would typically derive your own class from CFrame and place any modifications to the standard frame there. You can override the WndProc member function to include any additional messages you would like to handle.

A separate view window is placed over the client area of the frame window. Typically, this view window is created by inheriting a class from CWnd. The CFrame::SetView function is used to assign the view window to the frame. For MDI frames however, the CMDIFrame already uses CMDIClient as the view window, and you would use CMDIFrame::AddMDIChild to create a new instance of a MDI child window.

One of the important advantages of programming directly with the Windows API is that the code produced is portable, which is to say that it can be compiled on different compilers. The code in this library has been checked for compatibility with Visual C++ 6.0, Visual Studio .NET 2003, Visual C++ 2005 Express Edition, Visual C++ 2008 Express Edition, and also Dev-C++ version 4.9.9.2. Dev-C++ is a free C++ compiler and Integrated Development Environment available for download from here. The library is also compatible with Visual C++ Toolkit 2003 (a free compiler from Microsoft) and Borland's free Turbo C++ 2006.

A tutorial which provides step by step instructions for using Win32++ is available here.

Object oriented approach

The key to bringing an object oriented approach to programming directly with the Windows API is to have a C++ class that can create a window and which includes its own window procedure as a member function. Once we have this class, we can inherit from it and override the window procedure member function to handle messages the way we want for each derived window type.

Creating a class like this is not trivial, and I suspect that's one of the reasons why MFC was created in the first place. The problem stems from the way a "window class" is registered before the window can be created. (The term "class" here refers to the Windows API "window class", which is not the same thing as a C++ class.) The following code snippet shows how a window class might be registered using the API:

WNDCLASSEX wc;
memset((WNDCLASSEX*)&wc, 0, sizeof(WNDCLASSEX));

wc.cbSize = sizeof(WNDCLASSEX);
//The name of the window procedure
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = "TEST";
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

//Register the window class
::RegisterClassEx(&wc);

Note that we need to supply the function name of our window procedure. The window procedure is where we control what is to be done when a window message is received. This function must conform precisely to the predefined standards required by the Windows API. A typical declaration of the callback function looks like this:

LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg,
                         WPARAM wParam, LPARAM lParam);

We might be tempted to set the WindowProc function as a member of the class. Unfortunately, each class member function has an implicit this pointer as one of its arguments, and therefore cannot be used as the callback function for a window. If we did this, our WindowProc function would no longer conform to the predefined standards and the program would fail to compile.

We can make the WindowProc function a static member function of the class. There is no implicit this in a static function, and this will compile correctly. Unfortunately, a static member function doesn't have access to the class object (i.e., it doesn't have a this pointer), and it cannot access other members of the class. It is this which prevents the static member function from being used in an object oriented way. The following code demonstrates the limitations of a static member function approach:

class TestStatic
{
public:
  int member;

  void NormalFunction()
  {
    //We can access member variables in a normal
    //member function
    member = 5;

    //The following line is equivalent to the one above
    this->member = 5;
  }

  void static StaticFunction()
  {
    //We cannot access member variables
    //in a static member function
    //The following line will give a compile error
    member = 5;

    //This will give an error too
    this->member = 5;
  }
};

A static member function for the window procedure would be useful if we could just get our hands on a pointer to the window class object (our this pointer). There are a number of techniques that we can use to get access to our pointer as the window is being created. The one I have chosen takes advantage of Thread Local Storage to store our pointer, which is later inserted into an STL map. This is how it's done:

Step 1: Set up the Thread Local Storage to store our this pointer. This is done in the CWinApp class:

CWinApp::CWinApp(HINSTANCE hInstance) : m_hInstance(hInstance)
{
  if (GetApp() == 0)
  {
    st_dwTlsIndex = ::TlsAlloc();

    //snip

    }
}

Step 2: Store our this pointer in the Thread Local Storage when we use CreateEx to create the window:

// Ensure this thread has the TLS index set
TLSData* pTLSData = GetApp()->SetTlsIndex();

// Store the CWnd pointer in thread local storage
pTLSData->pCWnd = this;

Step 3: Extract the pointer from Thread Local Storage and add it to the STL map during the initial creation of the window:

// Retrieve the pointer to the TLS Data
TLSData* pTLSData = (TLSData*)TlsGetValue(GetApp()->GetTlsIndex());

// Retrieve pointer to CWnd object from Thread Local Storage TLS
w = pTLSData->pCWnd;

// Store the CWnd pointer in the HWND map
GetApp()->AddToMap(hWnd, w);

return w->WndProc(hWnd, uMsg, wParam, lParam);

Step 4: For each subsequent window message, we extract the pointer from the STL map and use it to redirect the message handling to the appropriate WndProc function:

CWnd* w = GetApp()->GetCWndFromMap(hWnd);
return w->WndProc(hWnd, uMsg, wParam, lParam);

Window creation in detail

Now that we've had a look at the window procedure it is time to see how these fit together as we create the window. This is the code which creates the window:

HWND CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName,
                 DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hParent,
                 HMENU hMenu, LPVOID lpParam /*= NULL*/)
{
  try
  {
    // Test if Win32++ has been started
    if (0 == GetApp())
      throw CWinException(_T("Win32++ has not been initialised properly.\n
                   Start the Win32++ by inheriting from CWinApp."));

    // Only one window per CWnd instance allowed
    if (::IsWindow(m_hWnd))
      throw CWinException(_T("CWnd::CreateEx ... Window already exists"));

    // Ensure a window class is registered
    TCHAR ClassName[MAX_STRING_SIZE] = _T("");
    if (0 == lstrlen(lpszClassName) )
      lstrcpyn (ClassName, _T("Win32++ Window"), MAX_STRING_SIZE);
    else
      // Create our own local copy of szClassName.
      lstrcpyn(ClassName, lpszClassName, MAX_STRING_SIZE);

    WNDCLASS wc = {0};
    wc.lpszClassName = ClassName;
    wc.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);
    wc.hCursor		 = ::LoadCursor(NULL, IDC_ARROW);
    if (!RegisterClass(wc))	// Register the window class (if not already registered)
      throw CWinException(_T("CWnd::CreateEx  Failed to register window class"));

    // Ensure this thread has the TLS index set
    TLSData* pTLSData = GetApp()->SetTlsIndex();

    // Store the CWnd pointer in thread local storage
    pTLSData->pCWnd = this;

    // Create window
    m_hWnd = ::CreateWindowEx(dwExStyle, ClassName, lpszWindowName, dwStyle, x, y, nWidth,
                       nHeight, hParent, hMenu, GetApp()->GetInstanceHandle(), lpParam);

    // Now handle window creation failure
    if (!m_hWnd)
      throw CWinException(_T("CWnd::CreateEx ... Failed to Create Window"));

    m_hWndParent = hParent;

    // Automatically subclass predefined window class types
    ::GetClassInfo(GetApp()->GetInstanceHandle(), lpszClassName, &wc);
    if (wc.lpfnWndProc != GetApp()->m_Callback)
    {
      Subclass();

      // Send a message to force the HWND to be added to the map
      ::SendMessage(m_hWnd, WM_NULL, 0, 0);

      OnCreate(); // We missed the WM_CREATE message, so call OnCreate now
    }

    // Clear the CWnd pointer from TLS
    pTLSData->pCWnd = NULL;

    // Window creation is complete. Now call OnInitialUpdate
    OnInitialUpdate();
  }

  catch (const CWinException &e)
  {
    e.MessageBox();
  }

  return m_hWnd;

} // HWND CWnd::CreateEx()

The next code segment is the window procedure which first receives the messages. We extract the pointer to the CWnd object from the map, and use it to redirect the handling of the window messages to the appropriate WndProc function:

LRESULT CALLBACK CWnd::StaticWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  try
  {
    CWnd* w = GetApp()->GetCWndFromMap(hWnd);
    if (0 != w)
    {
      // CWnd pointer found, so call the CWnd's WndProc
      return w->WndProc(hWnd, uMsg, wParam, lParam);
    }
    else
    {
      // The CWnd pointer wasn't found in the map, so add it now

      // Retrieve the pointer to the TLS Data
      TLSData* pTLSData = (TLSData*)TlsGetValue(GetApp()->GetTlsIndex());
      if (NULL == pTLSData)
        throw CWinException(_T("CWnd::StaticCBTProc ... Unable to get TLS"));

      // Retrieve pointer to CWnd object from Thread Local Storage TLS
      w = pTLSData->pCWnd;
      if (NULL == w)
        throw CWinException(_T("CWnd::StaticWindowProc .. Failed to route message"));

      pTLSData->pCWnd = NULL;

      // Store the CWnd pointer in the HWND map
      GetApp()->AddToMap(hWnd, w);

      // Store the HWND in the CWnd object early
      w->m_hWnd = hWnd;

      return w->WndProc(hWnd, uMsg, wParam, lParam);
    }
  }
//snip

Finally, the next code segment shows the function called by StaticWindowProc. Typically, when we derive a new class from CWnd, we would override this function to control the way various window messages are handled:

LRESULT CWnd::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  // Override this function in your class derrived from CWnd to handle
  //  window messages. A typical function might look like this:

  //  switch (uMsg)
  //  {
  //  case MESSAGE1:    // Some Windows API message
  //    OnMessage1();   // A user defined function
  //    break;          // Also do default processing
  //  case MESSAGE2:
  //    OnMessage2();
  //    return x;       // Don't do default processing, but instead return
  //                    //  a value recommended by the Windows API documentation
  //  }

  // Always pass unhandled messages on to WndProcDefault
  return WndProcDefault(hWnd, uMsg, wParam, lParam);
}

History

  • Mar, 2005: Version 1.0
    • Initial release.
  • Apr, 2005: Version 2.0
    • Added CDialog.
    • Added CWinApp.
  • Dec, 2005: Version 3.0
    • Added tracing.
    • Added support for windows in different threads.
  • Apr, 2006: Version 4.0
    • Used a map rather than the window's user data for storing the windows CWnd pointer. This allows the lpParam parameter to be used for user data when creating a window.
  • Dec, 2006: Version 5.0
    • Added CRebar, CMenubar, CSplitter.
    • Added MDI frames support.
    • Added Property Sheet support.
    • Added message reflection.
    • Added automatic subclassing.
    • Added multilingual support.
    • Added icons to menu icons.
    • Added skinning for rebars, toolbars and menus.
  • March, 2008: Version 6.0
    • Added Windows CE support
    • Added 64bit support.
    • Added CSocket for network support.
    • Added CDC to simplify using widow's Graphics Device Interface (GDI).
    • Enhanced the tutorial. It now also demonstrates file operations and printing.

Conclusion

With technique we are able to forward every window message to the appropriate CWnd object. No messages are discarded, even during window creation. This technique also supports all window types, including dialogs, common controls, MDI frames, and property sheets.


Accueil

Accueil CHM

Normal, dit comme ça,
vous ne risquez pas de comprendre.